001    /*
002     * $Id: JXTreeTable.java,v 1.33 2006/05/14 08:12:20 dmouse Exp $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     * 
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     * 
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    
022    
023    package org.jdesktop.swingx;
024    import java.awt.Component;
025    import java.awt.Dimension;
026    import java.awt.Graphics;
027    import java.awt.Point;
028    import java.awt.Rectangle;
029    import java.awt.event.ActionEvent;
030    import java.awt.event.InputEvent;
031    import java.awt.event.MouseEvent;
032    import java.beans.PropertyChangeEvent;
033    import java.beans.PropertyChangeListener;
034    import java.util.Enumeration;
035    import java.util.EventObject;
036    
037    import javax.swing.ActionMap;
038    import javax.swing.Icon;
039    import javax.swing.JComponent;
040    import javax.swing.JTable;
041    import javax.swing.JTree;
042    import javax.swing.ListSelectionModel;
043    import javax.swing.SwingUtilities;
044    import javax.swing.UIManager;
045    import javax.swing.border.Border;
046    import javax.swing.event.ListSelectionEvent;
047    import javax.swing.event.ListSelectionListener;
048    import javax.swing.event.TreeExpansionEvent;
049    import javax.swing.event.TreeExpansionListener;
050    import javax.swing.event.TreeModelEvent;
051    import javax.swing.event.TreeModelListener;
052    import javax.swing.event.TreeSelectionListener;
053    import javax.swing.event.TreeWillExpandListener;
054    import javax.swing.plaf.UIResource;
055    import javax.swing.table.AbstractTableModel;
056    import javax.swing.table.TableCellRenderer;
057    import javax.swing.table.TableModel;
058    import javax.swing.tree.DefaultTreeCellRenderer;
059    import javax.swing.tree.DefaultTreeSelectionModel;
060    import javax.swing.tree.TreeCellRenderer;
061    import javax.swing.tree.TreePath;
062    import javax.swing.tree.TreeSelectionModel;
063    
064    import org.jdesktop.swingx.decorator.ComponentAdapter;
065    import org.jdesktop.swingx.treetable.AbstractTreeTableModel;
066    import org.jdesktop.swingx.treetable.DefaultTreeTableModel;
067    import org.jdesktop.swingx.treetable.TreeTableCellEditor;
068    import org.jdesktop.swingx.treetable.TreeTableModel;
069    
070    
071    /**
072     * <p><code>JXTreeTable</code> is a specialized {@link javax.swing.JTable table}
073     * consisting of a single column in which to display hierarchical data, and any
074     * number of other columns in which to display regular data. The interface for
075     * the data model used by a <code>JXTreeTable</code> is
076     * {@link org.jdesktop.swingx.treetable.TreeTableModel}. It extends the
077     * {@link javax.swing.tree.TreeModel} interface to allow access to cell data by
078     * column indices within each node of the tree hierarchy.</p>
079     *
080     * <p>The most straightforward way create and use a <code>JXTreeTable</code>, is to
081     * first create a suitable data model for it, and pass that to a
082     * <code>JXTreeTable</code> constructor, as shown below:
083     * <pre>
084     *  TreeTableModel  treeTableModel = new FileSystemModel(); // any TreeTableModel
085     *  JXTreeTable      treeTable = new JXTreeTable(treeTableModel);
086     *  JScrollPane     scrollpane = new JScrollPane(treeTable);
087     * </pre>
088     * See {@link javax.swing.JTable} for an explanation of why putting the treetable
089     * inside a scroll pane is necessary.</p>
090     *
091     * <p>A single treetable model instance may be shared among more than one
092     * <code>JXTreeTable</code> instances. To access the treetable model, always call
093     * {@link #getTreeTableModel() getTreeTableModel} and
094     * {@link #setTreeTableModel(org.jdesktop.swingx.treetable.TreeTableModel) setTreeTableModel}.
095     * <code>JXTreeTable</code> wraps the supplied treetable model inside a private
096     * adapter class to adapt it to a {@link javax.swing.table.TableModel}. Although
097     * the model adapter is accessible through the {@link #getModel() getModel} method, you
098     * should avoid accessing and manipulating it in any way. In particular, each
099     * model adapter instance is tightly bound to a single table instance, and any
100     * attempt to share it with another table (for example, by calling
101     * {@link #setModel(javax.swing.table.TableModel) setModel})
102     * will throw an <code>IllegalArgumentException</code>!
103     *
104     * @author Philip Milne
105     * @author Scott Violet
106     * @author Ramesh Gupta
107     */
108    public class JXTreeTable extends JXTable {
109    
110        /**
111         * Key for clientProperty to decide whether to apply hack around #168-jdnc.
112         */
113        public static final String DRAG_HACK_FLAG_KEY = "treeTable.dragHackFlag";
114        /**
115         * Renderer used to render cells within the
116         *  {@link #isHierarchical(int) hierarchical} column.
117         *  renderer extends JXTree and implements TableCellRenderer
118         */
119        private TreeTableCellRenderer renderer;
120    
121        /**
122         * Constructs a JXTreeTable using a
123         * {@link org.jdesktop.swingx.treetable.DefaultTreeTableModel}.
124         */
125        public JXTreeTable() {
126            this(new DefaultTreeTableModel());
127        }
128    
129        /**
130         * Constructs a JXTreeTable using the specified
131         * {@link org.jdesktop.swingx.treetable.TreeTableModel}.
132         *
133         * @param treeModel model for the JXTreeTable
134         */
135        public JXTreeTable(TreeTableModel treeModel) {
136            // Implementation note:
137            // Make sure that the SAME instance of treeModel is passed to the
138            // constructor for TreeTableCellRenderer as is passed in the first
139            // argument to the following chained constructor for this JXTreeTable:
140            this(treeModel, new JXTreeTable.TreeTableCellRenderer(treeModel));
141        }
142    
143        /**
144         * Constructs a <code>JXTreeTable</code> using the specified
145         * {@link org.jdesktop.swingx.treetable.TreeTableModel} and
146         * {@link org.jdesktop.swingx.JXTreeTable.TreeTableCellRenderer}. The renderer
147         * must have been constructed using the same instance of
148         * {@link org.jdesktop.swingx.treetable.TreeTableModel} as passed to this
149         * constructor.
150         *
151         * @param treeModel model for the JXTreeTable
152         * @param renderer cell renderer for the tree portion of this JXTreeTable instance.
153         * @throws IllegalArgumentException if an attempt is made to instantiate
154         * JXTreeTable and TreeTableCellRenderer with different instances of TreeTableModel.
155         */
156        private JXTreeTable(TreeTableModel treeModel, TreeTableCellRenderer renderer) {
157            // To avoid unnecessary object creation, such as the construction of a
158            // DefaultTableModel, it is better to invoke super(TreeTableModelAdapter)
159            // directly, instead of first invoking super() followed by a call to
160            // setTreeTableModel(TreeTableModel).
161    
162            // Adapt tree model to table model before invoking super()
163            super(new TreeTableModelAdapter(treeModel, renderer));
164    
165            // Enforce referential integrity; bail on fail
166            if (treeModel != renderer.getModel()) { // do not use assert here!
167                throw new IllegalArgumentException("Mismatched TreeTableModel");
168            }
169    
170            // renderer-related initialization -- also called from setTreeTableModel()
171            init(renderer); // private method
172            initActions();
173            // disable sorting
174            super.setSortable(false);
175            // Install the default editor.
176            setDefaultEditor(AbstractTreeTableModel.hierarchicalColumnClass,
177                new TreeTableCellEditor(renderer));
178    
179            // No grid.
180            setShowGrid(false); // superclass default is "true"
181    
182            // Default intercell spacing
183            setIntercellSpacing(spacing); // for both row margin and column margin
184    
185            // JTable supports row margins and intercell spacing, but JTree doesn't.
186            // We must reconcile the differences in the semantics of rowHeight as
187            // understood by JTable and JTree by overriding both setRowHeight() and
188            // setRowMargin();
189            adminSetRowHeight(getRowHeight());
190            setRowMargin(getRowMargin()); // call overridden setRowMargin()
191    
192        }
193    
194    
195        private void initActions() {
196            // Register the actions that this class can handle.
197            ActionMap map = getActionMap();
198            map.put("expand-all", new Actions("expand-all"));
199            map.put("collapse-all", new Actions("collapse-all"));
200        }
201    
202        /**
203         * A small class which dispatches actions.
204         * TODO: Is there a way that we can make this static?
205         */
206        private class Actions extends UIAction {
207            Actions(String name) {
208                super(name);
209            }
210    
211            public void actionPerformed(ActionEvent evt) {
212                if ("expand-all".equals(getName())) {
213            expandAll();
214                }
215                else if ("collapse-all".equals(getName())) {
216                    collapseAll();
217                }
218            }
219        }
220    
221        /** 
222         * overridden to do nothing. 
223         * 
224         * TreeTable is not sortable by default, because 
225         * Sorters/Filters currently don't work properly.
226         * 
227         */
228        @Override
229        public void setSortable(boolean sortable) {
230            // no-op
231        }
232    
233        /**
234         * <p>Sets whether the table draws horizontal lines between cells. It draws
235         * the lines if <code>show</code> is true; otherwise it doesn't. By default,
236         * a table draws the lines.</p>
237         *
238         * <p>If you want the lines to be drawn, make sure that the row margin or
239         * horizontal intercell spacing is greater than zero.</p>
240         *
241         * @param show true, if horizontal lines should be drawn; false, if lines
242         * should not be drawn
243         * @see javax.swing.JTable#getShowHorizontalLines() getShowHorizontalLines
244         * @see #setRowMargin(int) setRowMargin
245         * @see javax.swing.JTable#setIntercellSpacing(java.awt.Dimension) setIntercellSpacing
246         */
247        @Override
248        public void setShowHorizontalLines(boolean show) {
249            super.setShowHorizontalLines(show);
250        }
251    
252        /**
253         * <p>Sets whether the table draws vertical lines between cells. It draws
254         * the lines if <code>show</code> is true; otherwise it doesn't. By default,
255         * a table draws the lines.</p>
256         *
257         * <p>If you want the lines to be drawn, make sure that the column margin or
258         * vertical intercell spacing is greater than zero.</p>
259         *
260         * @param show true, if vertical lines should be drawn; false, if lines
261         * should not be drawn
262         * @see javax.swing.JTable#getShowVerticalLines() getShowVerticalLines
263         * @see #setColumnMargin(int) setColumnMargin
264         * @see javax.swing.JTable#setIntercellSpacing(java.awt.Dimension) setIntercellSpacing
265         */
266        @Override
267        public void setShowVerticalLines(boolean show) {
268            super.setShowVerticalLines(show);
269        }
270    
271        /**
272         * Overriden to invoke repaint for the particular location if
273         * the column contains the tree. This is done as the tree editor does
274         * not fill the bounds of the cell, we need the renderer to paint
275         * the tree in the background, and then draw the editor over it.
276         * You should not need to call this method directly.
277         *
278         * {@inheritDoc}
279         */
280        @Override
281        public boolean editCellAt(int row, int column, EventObject e) {
282            expandOrCollapseNode(column, e);    // RG: Fix Issue 49!
283            boolean canEdit = super.editCellAt(row, column, e);
284            if (canEdit && isHierarchical(column)) {
285                repaint(getCellRect(row, column, false));
286            }
287            return canEdit;
288        }
289    
290        private void expandOrCollapseNode(int column, EventObject e) {
291            if (!(e instanceof MouseEvent)) return;
292            if (!isHierarchical(column)) return;
293            MouseEvent me = (MouseEvent) e;
294            if (hackAroundDragEnabled(me)) {
295                /*
296                 * Hack around #168-jdnc:
297                 * dirty little hack mentioned in the forum
298                 * discussion about the issue: fake a mousePressed if drag enabled.
299                 * The usability is slightly impaired because the expand/collapse
300                 * is effectively triggered on released only (drag system intercepts
301                 * and consumes all other).
302                 */
303                me = new MouseEvent((Component)me.getSource(),
304                        MouseEvent.MOUSE_PRESSED,
305                        me.getWhen(),
306                        me.getModifiers(),
307                        me.getX(),
308                        me.getY(),
309                        me.getClickCount(),
310                        me.isPopupTrigger());
311                
312            }
313            // If the modifiers are not 0 (or the left mouse button),
314            // tree may try and toggle the selection, and table
315            // will then try and toggle, resulting in the
316            // selection remaining the same. To avoid this, we
317            // only dispatch when the modifiers are 0 (or the left mouse
318            // button).
319            if (me.getModifiers() == 0 ||
320                me.getModifiers() == InputEvent.BUTTON1_MASK) {
321                        int savedHeight = renderer.getRowHeight();
322                        renderer.setRowHeight(getRowHeight());
323                        MouseEvent pressed = new MouseEvent
324                            (renderer,
325                             me.getID(),
326                             me.getWhen(),
327                             me.getModifiers(),
328                             me.getX() - getCellRect(0, column, false).x,
329                             me.getY(),
330                             me.getClickCount(),
331                             me.isPopupTrigger());
332                        renderer.dispatchEvent(pressed);
333                        // For Mac OS X, we need to dispatch a MOUSE_RELEASED as well
334                        MouseEvent released = new MouseEvent
335                            (renderer,
336                             java.awt.event.MouseEvent.MOUSE_RELEASED,
337                             pressed.getWhen(),
338                             pressed.getModifiers(),
339                             pressed.getX(),
340                             pressed.getY(),
341                             pressed.getClickCount(),
342                             pressed.isPopupTrigger());
343                        renderer.dispatchEvent(released);
344                        renderer.setRowHeight(savedHeight);
345            }
346        }
347    
348        /**
349         * decides whether we want to apply the hack for #168-jdnc.
350         * here: returns true if dragEnabled() and the improved drag
351         * handling is not activated (or the system property is not accessible).
352         * The given mouseEvent is not analysed.
353         * 
354         * PENDING: Mustang?
355         * 
356         * @param me the mouseEvent that triggered a editCellAt
357         * @return true if the hack should be applied.
358         */
359        protected boolean hackAroundDragEnabled(MouseEvent me) {
360            Boolean dragHackFlag = (Boolean) getClientProperty(DRAG_HACK_FLAG_KEY);
361            if (dragHackFlag == null) {
362                // access and store the system property as a client property once
363                String priority = null;
364                try {
365                    priority = System.getProperty("sun.swing.enableImprovedDragGesture");
366    
367                } catch (Exception ex) {
368                    // found some foul expression or failed to read the property
369                }
370                dragHackFlag = (priority == null);
371                putClientProperty(DRAG_HACK_FLAG_KEY, dragHackFlag);
372            }
373            return getDragEnabled() && dragHackFlag;
374        }
375    
376        /**
377         * Overridden to provide a workaround for BasicTableUI anomaly. Make sure
378         * the UI never tries to resize the editor. The UI currently uses different
379         * techniques to paint the renderers and editors. So, overriding setBounds()
380         * is not the right thing to do for an editor. Returning -1 for the
381         * editing row in this case, ensures the editor is never painted.
382         *
383         * {@inheritDoc}
384         */
385        @Override
386        public int getEditingRow() {
387            return isHierarchical(editingColumn) ? -1 : editingRow;
388        }
389    
390        /**
391         * Returns the actual row that is editing as <code>getEditingRow</code>
392         * will always return -1.
393         */
394        private int realEditingRow() {
395            return editingRow;
396        }
397    
398        /**
399         * Sets the data model for this JXTreeTable to the specified
400         * {@link org.jdesktop.swingx.treetable.TreeTableModel}. The same data model
401         * may be shared by any number of JXTreeTable instances.
402         *
403         * @param treeModel data model for this JXTreeTable
404         */
405        public void setTreeTableModel(TreeTableModel treeModel) {
406            TreeTableModel old = getTreeTableModel();
407            renderer.setModel(treeModel);
408            ((TreeTableModelAdapter)getModel()).setTreeTableModel(treeModel);
409            // Enforce referential integrity; bail on fail
410            // JW: when would that happen? we just set it... 
411            if (treeModel != renderer.getModel()) { // do not use assert here!
412                throw new IllegalArgumentException("Mismatched TreeTableModel");
413            }
414            // Install the default editor.
415            // JW: change to re-use the editor!
416            setDefaultEditor(AbstractTreeTableModel.hierarchicalColumnClass,
417                new TreeTableCellEditor(renderer));
418    
419            // JTable supports row margins and intercell spacing, but JTree doesn't.
420            // We must reconcile the differences in the semantics of rowHeight as
421            // understood by JTable and JTree by overriding both setRowHeight() and
422            // setRowMargin();
423            adminSetRowHeight(getRowHeight());
424            setRowMargin(getRowMargin()); // call overridden setRowMargin()
425            firePropertyChange("treeTableModel", old, getTreeTableModel());
426        }
427    
428        /**
429         * Returns the underlying TreeTableModel for this JXTreeTable.
430         *
431         * @return the underlying TreeTableModel for this JXTreeTable
432         */
433        public TreeTableModel getTreeTableModel() {
434            return ((TreeTableModelAdapter) getModel()).getTreeTableModel();
435        }
436    
437        /**
438         * <p>Overrides superclass version to make sure that the specified
439         * {@link javax.swing.table.TableModel} is compatible with JXTreeTable before
440         * invoking the inherited version.</p>
441         *
442         * <p>Because JXTreeTable internally adapts an
443         * {@link org.jdesktop.swingx.treetable.TreeTableModel} to make it a compatible
444         * TableModel, <b>this method should never be called directly</b>. Use
445         * {@link #setTreeTableModel(org.jdesktop.swingx.treetable.TreeTableModel) setTreeTableModel} instead.</p>
446         *
447         * <p>While it is possible to obtain a reference to this adapted
448         * version of the TableModel by calling {@link javax.swing.JTable#getModel()},
449         * any attempt to call setModel() with that adapter will fail because
450         * the adapter might have been bound to a different JXTreeTable instance. If
451         * you want to extract the underlying TreeTableModel, which, by the way,
452         * <em>can</em> be shared, use {@link #getTreeTableModel() getTreeTableModel}
453         * instead</p>.
454         *
455         * @param tableModel must be a TreeTableModelAdapter
456         * @throws IllegalArgumentException if the specified tableModel is not an
457         * instance of TreeTableModelAdapter
458         */
459        @Override
460        public final void setModel(TableModel tableModel) { // note final keyword
461            if (tableModel instanceof TreeTableModelAdapter) {
462                if (((TreeTableModelAdapter) tableModel).getTreeTable() == null) {
463                    // Passing the above test ensures that this method is being
464                    // invoked either from JXTreeTable/JTable constructor or from
465                    // setTreeTableModel(TreeTableModel)
466                    super.setModel(tableModel); // invoke superclass version
467    
468                    ((TreeTableModelAdapter) tableModel).bind(this); // permanently bound
469                    // Once a TreeTableModelAdapter is bound to any JXTreeTable instance,
470                    // invoking JXTreeTable.setModel() with that adapter will throw an
471                    // IllegalArgumentException, because we really want to make sure
472                    // that a TreeTableModelAdapter is NOT shared by another JXTreeTable.
473                }
474                else {
475                    throw new IllegalArgumentException("model already bound");
476                }
477            }
478            else {
479                throw new IllegalArgumentException("unsupported model type");
480            }
481        }
482    
483        /**
484         * Throws UnsupportedOperationException because variable height rows are
485         * not supported.
486         *
487         * @param row ignored
488         * @param rowHeight ignored
489         * @throws UnsupportedOperationException because variable height rows are
490         * not supported
491         */
492        @Override
493        public final void setRowHeight(int row, int rowHeight) {
494            throw new UnsupportedOperationException("variable height rows not supported");
495        }
496    
497        /**
498         * Sets the row height for this JXTreeTable. Reconciles semantic differences
499         * between JTable and JTree regarding row height.
500         *
501         * @param rowHeight height of a row
502         */
503        @Override
504        public void setRowHeight(int rowHeight) {
505            super.setRowHeight(rowHeight);
506            adjustTreeRowHeight(); // JTree doesn't have setRowMargin. So adjust.
507        }
508    
509        /**
510         * <p>Sets the margin between columns.</p>
511         *
512         * <p>If you set the column margin to zero, make sure that you also set
513         * <code>showVerticalLines</code> to <code>false</code>.</p>
514         *
515         * @param columnMargin margin between columns; must be greater than or equal to zero.
516         * @see #setShowVerticalLines(boolean) setShowVerticalLines
517         */
518        @Override
519        public void setColumnMargin(int columnMargin) {
520            super.setColumnMargin(columnMargin);
521        }
522    
523        /**
524         * <p>Overridden to ensure that private renderer state is kept in sync with the
525         * state of the component. Calls the inherited version after performing the
526         * necessary synchronization. If you override this method, make sure you call
527         * this version from your version of this method.</p>
528         *
529         * <p>If you set row margin to zero, make sure that you also set
530         * <code>showHorizontalLines</code> to <code>false</code>.</p>
531         *
532         * @param rowMargin margin or intercell spacing between rows
533         * @see #setShowHorizontalLines(boolean) setShowHorizontalLines
534         */
535        @Override
536        public void setRowMargin(int rowMargin) {
537            // No need to override setIntercellSpacing, because the change in
538            // rowMargin will be funneled through this method anyway.
539            super.setRowMargin(rowMargin);
540            adjustTreeRowHeight(); // JTree doesn't have setRowMargin. So adjust.
541        }
542    
543        /**
544         * Reconciles semantic differences between JTable and JTree regarding
545         * row height.
546         */
547        private void adjustTreeRowHeight() {
548            final int treeRowHeight = rowHeight + (rowMargin << 1);
549            if (renderer != null && renderer.getRowHeight() != treeRowHeight) {
550                renderer.setRowHeight(treeRowHeight);
551            }
552        }
553    
554        /**
555         * <p>Overridden to ensure that private renderer state is kept in sync with the
556         * state of the component. Calls the inherited version after performing the
557         * necessary synchronization. If you override this method, make sure you call
558         * this version from your version of this method.</p>
559         *
560         * <p>This version maps the selection mode used by the renderer to match the
561         * selection mode specified for the table. Specifically, the modes are mapped
562         * as follows:
563         * <pre>
564         *  ListSelectionModel.SINGLE_INTERVAL_SELECTION: TreeSelectionModel.CONTIGUOUS_TREE_SELECTION;
565         *  ListSelectionModel.MULTIPLE_INTERVAL_SELECTION: TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION;
566         *  any other (default): TreeSelectionModel.SINGLE_TREE_SELECTION;
567         * </pre>
568         *
569         * {@inheritDoc}
570         *
571         * @param mode any of the table selection modes
572         */
573        @Override
574        public void setSelectionMode(int mode) {
575            if (renderer != null) {
576                switch (mode) {
577                    case ListSelectionModel.SINGLE_INTERVAL_SELECTION: {
578                        renderer.getSelectionModel().setSelectionMode(
579                            TreeSelectionModel.CONTIGUOUS_TREE_SELECTION);
580                        break;
581                    }
582                    case ListSelectionModel.MULTIPLE_INTERVAL_SELECTION: {
583                        renderer.getSelectionModel().setSelectionMode(
584                            TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
585                        break;
586                    }
587                    default: {
588                        renderer.getSelectionModel().setSelectionMode(
589                            TreeSelectionModel.SINGLE_TREE_SELECTION);
590                        break;
591                    }
592                }
593            }
594            super.setSelectionMode(mode);
595        }
596    
597        /**
598         * Overrides superclass version to provide support for cell decorators.
599         *
600         * @param renderer the <code>TableCellRenderer</code> to prepare
601         * @param row the row of the cell to render, where 0 is the first row
602         * @param column the column of the cell to render, where 0 is the first column
603         * @return the <code>Component</code> used as a stamp to render the specified cell
604         */
605        @Override
606        public Component prepareRenderer(TableCellRenderer renderer, int row,
607            int column) {
608            
609            Component component = super.prepareRenderer(renderer, row, column);
610            // MUST ALWAYS ACCESS dataAdapter through accessor method!!!
611            ComponentAdapter    adapter = getComponentAdapter();
612            adapter.row = row;
613            adapter.column = column;
614            
615            return applyRenderer(component, adapter); 
616        }
617    
618        /**
619         * Performs necessary housekeeping before the renderer is actually applied.
620         *
621         * @param component
622         * @param adapter component data adapter
623         * @throws NullPointerException if the specified component or adapter is null
624         */
625        protected Component applyRenderer(Component component,
626            ComponentAdapter adapter) {
627            if (component == null) {
628                throw new IllegalArgumentException("null component");
629            }
630            if (adapter == null) {
631                throw new IllegalArgumentException("null component data adapter");
632            }
633    
634            if (isHierarchical(adapter.column)) {
635                // After all decorators have been applied, make sure that relevant
636                // attributes of the table cell renderer are applied to the
637                // tree cell renderer before the hierarchical column is rendered!
638                TreeCellRenderer tcr = renderer.getCellRenderer();
639                if (tcr instanceof JXTree.DelegatingRenderer) {
640                    tcr = ((JXTree.DelegatingRenderer) tcr).getDelegateRenderer();
641                    
642                }
643                if (tcr instanceof DefaultTreeCellRenderer) {
644                    DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer) tcr);
645                    if (adapter.isSelected()) {
646                        dtcr.setTextSelectionColor(component.getForeground());
647                        dtcr.setBackgroundSelectionColor(component.getBackground());
648                   } else {
649                        dtcr.setTextNonSelectionColor(component.getForeground());
650                        dtcr.setBackgroundNonSelectionColor(component.getBackground());
651                    }
652                } 
653            }
654            return component;
655        }
656    
657        /**
658         * Sets the specified TreeCellRenderer as the Tree cell renderer.
659         *
660         * @param cellRenderer to use for rendering tree cells.
661         */
662        public void setTreeCellRenderer(TreeCellRenderer cellRenderer) {
663            if (renderer != null) {
664                renderer.setCellRenderer(cellRenderer);
665            }
666        }
667    
668        public TreeCellRenderer getTreeCellRenderer() {
669            return renderer.getCellRenderer();
670        }
671    
672        
673        @Override
674        public String getToolTipText(MouseEvent event) {
675            int column = columnAtPoint(event.getPoint());
676            if (isHierarchical(column)) {
677                int row = rowAtPoint(event.getPoint());
678                return renderer.getToolTipText(event, row, column);
679            }
680            return super.getToolTipText(event);
681        }
682        
683        /**
684         * Sets the specified icon as the icon to use for rendering collapsed nodes.
685         *
686         * @param icon to use for rendering collapsed nodes
687         */
688        public void setCollapsedIcon(Icon icon) {
689            renderer.setCollapsedIcon(icon);
690        }
691    
692        /**
693         * Sets the specified icon as the icon to use for rendering expanded nodes.
694         *
695         * @param icon to use for rendering expanded nodes
696         */
697        public void setExpandedIcon(Icon icon) {
698            renderer.setExpandedIcon(icon);
699        }
700    
701        /**
702         * Sets the specified icon as the icon to use for rendering open container nodes.
703         *
704         * @param icon to use for rendering open nodes
705         */
706        public void setOpenIcon(Icon icon) {
707            renderer.setOpenIcon(icon);
708        }
709    
710        /**
711         * Sets the specified icon as the icon to use for rendering closed container nodes.
712         *
713         * @param icon to use for rendering closed nodes
714         */
715        public void setClosedIcon(Icon icon) {
716            renderer.setClosedIcon(icon);
717        }
718    
719        /**
720         * Sets the specified icon as the icon to use for rendering leaf nodes.
721         *
722         * @param icon to use for rendering leaf nodes
723         */
724        public void setLeafIcon(Icon icon) {
725            renderer.setLeafIcon(icon);
726        }
727    
728        /**
729         * Overridden to ensure that private renderer state is kept in sync with the
730         * state of the component. Calls the inherited version after performing the
731         * necessary synchronization. If you override this method, make sure you call
732         * this version from your version of this method.
733         */
734        @Override
735        public void clearSelection() {
736            if (renderer != null) {
737                renderer.clearSelection();
738            }
739            super.clearSelection();
740        }
741    
742        /**
743         * Collapses all nodes in the treetable.
744         */
745        public void collapseAll() {
746            renderer.collapseAll();
747        }
748    
749        /**
750         * Expands all nodes in the treetable.
751         */
752        public void expandAll() {
753            renderer.expandAll();
754        }
755    
756        /**
757         * Collapses the node at the specified path in the treetable.
758         *
759         * @param path path of the node to collapse
760         */
761        public void collapsePath(TreePath path) {
762            renderer.collapsePath(path);
763        }
764    
765        /**
766         * Expands the the node at the specified path in the treetable.
767         *
768         * @param path path of the node to expand
769         */
770        public void expandPath(TreePath path) {
771            renderer.expandPath(path);
772        }
773    
774        /**
775         * Makes sure all the path components in path are expanded (except
776         * for the last path component) and scrolls so that the 
777         * node identified by the path is displayed. Only works when this
778         * <code>JTree</code> is contained in a <code>JScrollPane</code>.
779         * 
780         * (doc copied from JTree)
781         * 
782         * PENDING: JW - where exactly do we want to scroll? Here: the scroll
783         * is in vertical direction only. Might need to show the tree column?
784         * 
785         * @param path  the <code>TreePath</code> identifying the node to
786         *          bring into view
787         */
788        public void scrollPathToVisible(TreePath path) {
789            if (path == null) return;
790            renderer.makeVisible(path);
791            int row = getRowForPath(path);
792            scrollRowToVisible(row);
793        }
794    
795        
796        /**
797         * Collapses the row in the treetable. If the specified row index is
798         * not valid, this method will have no effect.
799         */
800        public void collapseRow(int row) {
801            renderer.collapseRow(row);
802        }
803    
804        /**
805         * Expands the specified row in the treetable. If the specified row index is
806         * not valid, this method will have no effect.
807         */
808        public void expandRow(int row) {
809            renderer.expandRow(row);
810        }
811    
812        
813        /**
814         * Determines whether or not the root node from the TreeModel is visible.
815         *
816         * @param visible true, if the root node is visible; false, otherwise
817         */
818        public void setRootVisible(boolean visible) {
819            renderer.setRootVisible(visible);
820            // JW: the revalidate forces the root to appear after a 
821            // toggling a visible from an initially invisible root.
822            // JTree fires a propertyChange on the ROOT_VISIBLE_PROPERTY
823            // BasicTreeUI reacts by (ultimately) calling JTree.treeDidChange
824            // which revalidate the tree part. 
825            // Might consider to listen for the propertyChange (fired only if there
826            // actually was a change) instead of revalidating unconditionally.
827            revalidate();
828            repaint();
829        }
830    
831        /**
832         * Returns true if the root node of the tree is displayed.
833         *
834         * @return true if the root node of the tree is displayed
835         */
836        public boolean isRootVisible() {
837            return renderer.isRootVisible();
838        }
839    
840        /**
841         * Returns true if the value identified by path is currently viewable, which
842         * means it is either the root or all of its parents are expanded. Otherwise,
843         * this method returns false.
844         *
845         * @return true, if the value identified by path is currently viewable;
846         * false, otherwise
847         */
848        public boolean isVisible(TreePath path) {
849            return renderer.isVisible(path);
850        }
851    
852        /**
853         * Returns true if the node identified by path is currently expanded.
854         * Otherwise, this method returns false.
855         *
856         * @param path path
857         * @return true, if the value identified by path is currently expanded;
858         * false, otherwise
859         */
860        public boolean isExpanded(TreePath path) {
861            return renderer.isExpanded(path);
862        }
863    
864        /**
865         * Returns true if the node at the specified display row is currently expanded.
866         * Otherwise, this method returns false.
867         *
868         * @param row row
869         * @return true, if the node at the specified display row is currently expanded.
870         * false, otherwise
871         */
872        public boolean isExpanded(int row) {
873            return renderer.isExpanded(row);
874        }
875    
876        /**
877         * Returns true if the node identified by path is currently collapsed, 
878         * this will return false if any of the values in path are currently not 
879         * being displayed.   
880         *
881         * @param path path
882         * @return true, if the value identified by path is currently collapsed;
883         * false, otherwise
884         */
885        public boolean isCollapsed(TreePath path) {
886            return renderer.isCollapsed(path);
887        }
888    
889        /**
890         * Returns true if the node at the specified display row is collapsed.
891         *
892         * @param row row
893         * @return true, if the node at the specified display row is currently collapsed.
894         * false, otherwise
895         */
896        public boolean isCollapsed(int row) {
897            return renderer.isCollapsed(row);
898        }
899    
900        
901        /**
902         * Returns an <code>Enumeration</code> of the descendants of the
903         * path <code>parent</code> that
904         * are currently expanded. If <code>parent</code> is not currently
905         * expanded, this will return <code>null</code>.
906         * If you expand/collapse nodes while
907         * iterating over the returned <code>Enumeration</code>
908         * this may not return all
909         * the expanded paths, or may return paths that are no longer expanded.
910         *
911         * @param parent  the path which is to be examined
912         * @return an <code>Enumeration</code> of the descendents of 
913         *      <code>parent</code>, or <code>null</code> if
914         *      <code>parent</code> is not currently expanded
915         */
916        
917        public Enumeration getExpandedDescendants(TreePath parent) {
918            return renderer.getExpandedDescendants(parent);
919        }
920    
921        
922        /**
923         * Sets the value of the <code>expandsSelectedPaths</code> property for the tree
924         * part. This property specifies whether the selected paths should be expanded.
925         *
926         * @param expand true, if selected paths should be expanded; false, otherwise
927         */
928        public void setExpandsSelectedPaths(boolean expand) {
929            renderer.setExpandsSelectedPaths(expand);
930        }
931    
932        /**
933         * Returns the value of the <code>expandsSelectedPaths</code> property.
934         *
935         * @return the value of the <code>expandsSelectedPaths</code> property
936         */
937        public boolean getExpandsSelectedPaths() {
938            return renderer.getExpandsSelectedPaths();
939        }
940    
941        /**
942         * Returns the TreePath for a given x,y location.
943         *
944         * @param x x value
945         * @param y y value
946         *
947         * @return the <code>TreePath</code> for the givern location.
948         */
949         public TreePath getPathForLocation(int x, int y) {
950            int row = rowAtPoint(new Point(x,y));
951            if (row == -1) {
952              return null;  
953            }
954            return renderer.getPathForRow(row);
955         }
956    
957        /**
958         * Returns the TreePath for a given row.
959         *
960         * @param row
961         *
962         * @return the <code>TreePath</code> for the given row.
963         */
964         public TreePath getPathForRow(int row) {
965            return renderer.getPathForRow(row);
966         }
967    
968         /**
969          * Returns the row for a given TreePath.
970          *
971          * @param path
972          * @return the row for the given <code>TreePath</code>.
973          */
974         public int getRowForPath(TreePath path) {
975           return renderer.getRowForPath(path);
976         }
977    
978        /**
979         * Sets the value of the <code>scrollsOnExpand</code> property for the tree
980         * part. This property specifies whether the expanded paths should be scrolled
981         * into view. In a look and feel in which a tree might not need to scroll
982         * when expanded, this property may be ignored.
983         *
984         * @param scroll true, if expanded paths should be scrolled into view;
985         * false, otherwise
986         */
987        public void setScrollsOnExpand(boolean scroll) {
988            renderer.setScrollsOnExpand(scroll);
989        }
990    
991        /**
992         * Returns the value of the <code>scrollsOnExpand</code> property.
993         *
994         * @return the value of the <code>scrollsOnExpand</code> property
995         */
996        public boolean getScrollsOnExpand() {
997            return renderer.getScrollsOnExpand();
998        }
999    
1000        /**
1001         * Sets the value of the <code>showsRootHandles</code> property for the tree
1002         * part. This property specifies whether the node handles should be displayed.
1003         * If handles are not supported by a particular look and feel, this property
1004         * may be ignored.
1005         *
1006         * @param visible true, if root handles should be shown; false, otherwise
1007         */
1008        public void setShowsRootHandles(boolean visible) {
1009            renderer.setShowsRootHandles(visible);
1010            repaint();
1011        }
1012    
1013        /**
1014         * Returns the value of the <code>showsRootHandles</code> property.
1015         *
1016         * @return the value of the <code>showsRootHandles</code> property
1017         */
1018        public boolean getShowsRootHandles() {
1019            return renderer.getShowsRootHandles();
1020        }
1021    
1022        /**
1023         * Adds a listener for <code>TreeExpansion</code> events.
1024         * 
1025         * TODO (JW): redirect event source to this. 
1026         * 
1027         * @param tel a TreeExpansionListener that will be notified 
1028         * when a tree node is expanded or collapsed
1029         */
1030        public void addTreeExpansionListener(TreeExpansionListener tel) {
1031            renderer.addTreeExpansionListener(tel);
1032        }
1033    
1034        /**
1035         * Removes a listener for <code>TreeExpansion</code> events.
1036         * @param tel the <code>TreeExpansionListener</code> to remove
1037         */
1038        public void removeTreeExpansionListener(TreeExpansionListener tel) {
1039            renderer.removeTreeExpansionListener(tel);
1040        }
1041    
1042        /**
1043         * Adds a listener for <code>TreeSelection</code> events.
1044         * TODO (JW): redirect event source to this. 
1045         * 
1046         * @param tsl a TreeSelectionListener that will be notified 
1047         * when a tree node is selected or deselected
1048         */
1049        public void addTreeSelectionListener(TreeSelectionListener tsl) {
1050            renderer.addTreeSelectionListener(tsl);
1051        }
1052    
1053        /**
1054         * Removes a listener for <code>TreeSelection</code> events.
1055         * @param tsl the <code>TreeSelectionListener</code> to remove
1056         */
1057        public void removeTreeSelectionListener(TreeSelectionListener tsl) {
1058            renderer.removeTreeSelectionListener(tsl);
1059        }
1060    
1061        /**
1062         * Adds a listener for <code>TreeWillExpand</code> events.
1063         * TODO (JW): redirect event source to this. 
1064         * 
1065         * @param tel a TreeWillExpandListener that will be notified 
1066         * when a tree node will be expanded or collapsed 
1067         */
1068        public void addTreeWillExpandListener(TreeWillExpandListener tel) {
1069            renderer.addTreeWillExpandListener(tel);
1070        }
1071    
1072        /**
1073         * Removes a listener for <code>TreeWillExpand</code> events.
1074         * @param tel the <code>TreeWillExpandListener</code> to remove
1075         */
1076        public void removeTreeWillExpandListener(TreeWillExpandListener tel) {
1077            renderer.removeTreeWillExpandListener(tel);
1078         }
1079     
1080        
1081        /**
1082         * Returns the selection model for the tree portion of the this treetable.
1083         *
1084         * @return selection model for the tree portion of the this treetable
1085         */
1086        public TreeSelectionModel getTreeSelectionModel() {
1087            return renderer.getSelectionModel();    // RG: Fix JDNC issue 41
1088        }
1089    
1090        /**
1091         * Overriden to invoke supers implementation, and then,
1092         * if the receiver is editing a Tree column, the editors bounds is
1093         * reset. The reason we have to do this is because JTable doesn't
1094         * think the table is being edited, as <code>getEditingRow</code> returns
1095         * -1, and therefore doesn't automaticly resize the editor for us.
1096         */
1097        @Override
1098        public void sizeColumnsToFit(int resizingColumn) {
1099            /** TODO: Review wrt doLayout() */
1100            super.sizeColumnsToFit(resizingColumn);
1101            // rg:changed
1102            if (getEditingColumn() != -1 && isHierarchical(editingColumn)) {
1103                Rectangle cellRect = getCellRect(realEditingRow(),
1104                    getEditingColumn(), false);
1105                Component component = getEditorComponent();
1106                component.setBounds(cellRect);
1107                component.validate();
1108            }
1109        }
1110    
1111        /**
1112         * Overridden to message super and forward the method to the tree.
1113         * Since the tree is not actually in the component hieachy it will
1114         * never receive this unless we forward it in this manner.
1115         */
1116        @Override
1117        public void updateUI() {
1118            super.updateUI();
1119            if (renderer != null) {
1120                //  final int savedHeight = renderer.getRowHeight();
1121                renderer.updateUI();
1122                //  renderer.setRowHeight(savedHeight);
1123    
1124                // Do this so that the editor is referencing the current renderer
1125                // from the tree. The renderer can potentially change each time
1126                // laf changes.
1127                setDefaultEditor(AbstractTreeTableModel.hierarchicalColumnClass,
1128                    new TreeTableCellEditor(renderer));
1129    
1130                if (getBackground() == null || getBackground()instanceof UIResource) {
1131                    setBackground(renderer.getBackground());
1132                }
1133            }
1134        }
1135    
1136        /**
1137         * Determines if the specified column contains hierarchical nodes.
1138         *
1139         * @param column zero-based index of the column
1140         * @return true if the class of objects in the specified column implement
1141         * the {@link javax.swing.tree.TreeNode} interface; false otherwise.
1142         */
1143        @Override
1144        public boolean isHierarchical(int column) {
1145            return AbstractTreeTableModel.hierarchicalColumnClass.isAssignableFrom(
1146                getColumnClass(column));
1147        }
1148    
1149        /**
1150         * Initializes this JXTreeTable and permanently binds the specified renderer
1151         * to it.
1152         *
1153         * @param renderer private tree/renderer permanently and exclusively bound
1154         * to this JXTreeTable.
1155         */
1156        private void init(TreeTableCellRenderer renderer) {
1157            this.renderer = renderer;
1158            // Force the JTable and JTree to share their row selection models.
1159            ListToTreeSelectionModelWrapper selectionWrapper =
1160                new ListToTreeSelectionModelWrapper();
1161    
1162            // JW: when would that happen?
1163            if (renderer != null) {
1164                renderer.bind(this); // IMPORTANT: link back!
1165                renderer.setSelectionModel(selectionWrapper);
1166            }
1167    
1168            setSelectionModel(selectionWrapper.getListSelectionModel());
1169            setDefaultRenderer(AbstractTreeTableModel.hierarchicalColumnClass,
1170                renderer);
1171            
1172            // propagate the lineStyle property to the renderer
1173            PropertyChangeListener l = new PropertyChangeListener() {
1174    
1175                public void propertyChange(PropertyChangeEvent evt) {
1176                    JXTreeTable.this.renderer.putClientProperty(evt.getPropertyName(), evt.getNewValue());
1177                    
1178                }
1179                
1180            };
1181            addPropertyChangeListener("JTree.lineStyle", l);
1182            
1183        }
1184    
1185    
1186        /**
1187         * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
1188         * to listen for changes in the ListSelectionModel it maintains. Once
1189         * a change in the ListSelectionModel happens, the paths are updated
1190         * in the DefaultTreeSelectionModel.
1191         */
1192        class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel {
1193            /** Set to true when we are updating the ListSelectionModel. */
1194            protected boolean updatingListSelectionModel;
1195    
1196            public ListToTreeSelectionModelWrapper() {
1197                super();
1198                getListSelectionModel().addListSelectionListener
1199                    (createListSelectionListener());
1200            }
1201    
1202            /**
1203             * Returns the list selection model. ListToTreeSelectionModelWrapper
1204             * listens for changes to this model and updates the selected paths
1205             * accordingly.
1206             */
1207            ListSelectionModel getListSelectionModel() {
1208                return listSelectionModel;
1209            }
1210    
1211            /**
1212             * This is overridden to set <code>updatingListSelectionModel</code>
1213             * and message super. This is the only place DefaultTreeSelectionModel
1214             * alters the ListSelectionModel.
1215             */
1216            @Override
1217            public void resetRowSelection() {
1218                if (!updatingListSelectionModel) {
1219                    updatingListSelectionModel = true;
1220                    try {
1221                        super.resetRowSelection();
1222                    }
1223                    finally {
1224                        updatingListSelectionModel = false;
1225                    }
1226                }
1227                // Notice how we don't message super if
1228                // updatingListSelectionModel is true. If
1229                // updatingListSelectionModel is true, it implies the
1230                // ListSelectionModel has already been updated and the
1231                // paths are the only thing that needs to be updated.
1232            }
1233    
1234            /**
1235             * Creates and returns an instance of ListSelectionHandler.
1236             */
1237            protected ListSelectionListener createListSelectionListener() {
1238                return new ListSelectionHandler();
1239            }
1240    
1241            /**
1242             * If <code>updatingListSelectionModel</code> is false, this will
1243             * reset the selected paths from the selected rows in the list
1244             * selection model.
1245             */
1246            protected void updateSelectedPathsFromSelectedRows() {
1247                if (!updatingListSelectionModel) {
1248                    updatingListSelectionModel = true;
1249                    try {
1250                        // This is way expensive, ListSelectionModel needs an
1251                        // enumerator for iterating.
1252                        int min = listSelectionModel.getMinSelectionIndex();
1253                        int max = listSelectionModel.getMaxSelectionIndex();
1254    
1255                        clearSelection();
1256                        if (min != -1 && max != -1) {
1257                            for (int counter = min; counter <= max; counter++) {
1258                                if (listSelectionModel.isSelectedIndex(counter)) {
1259                                    TreePath selPath = renderer.getPathForRow(
1260                                        counter);
1261    
1262                                    if (selPath != null) {
1263                                        addSelectionPath(selPath);
1264                                    }
1265                                }
1266                            }
1267                        }
1268                    }
1269                    finally {
1270                        updatingListSelectionModel = false;
1271                    }
1272                }
1273            }
1274    
1275            /**
1276             * Class responsible for calling updateSelectedPathsFromSelectedRows
1277             * when the selection of the list changse.
1278             */
1279            class ListSelectionHandler implements ListSelectionListener {
1280                public void valueChanged(ListSelectionEvent e) {
1281                    updateSelectedPathsFromSelectedRows();
1282                }
1283            }
1284        }
1285    
1286        private static class TreeTableModelAdapter extends AbstractTableModel {
1287            private TreeModelListener treeModelListener;
1288            /**
1289             * Maintains a TreeTableModel and a JTree as purely implementation details.
1290             * Developers can plug in any type of custom TreeTableModel through a
1291             * JXTreeTable constructor or through setTreeTableModel().
1292             *
1293             * @param model Underlying data model for the JXTreeTable that will ultimately
1294             * be bound to this TreeTableModelAdapter
1295             * @param tree TreeTableCellRenderer instantiated with the same model as
1296             * specified by the model parameter of this constructor
1297             * @throws IllegalArgumentException if a null model argument is passed
1298             * @throws IllegalArgumentException if a null tree argument is passed
1299             */
1300            TreeTableModelAdapter(TreeTableModel model, JTree tree) {
1301                assert model != null;
1302                assert tree != null;
1303    
1304                this.tree = tree; // need tree to implement getRowCount()
1305                setTreeTableModel(model);
1306    
1307                tree.addTreeExpansionListener(new TreeExpansionListener() {
1308                    // Don't use fireTableRowsInserted() here; the selection model
1309                    // would get updated twice.
1310                    public void treeExpanded(TreeExpansionEvent event) {
1311                        fireTableDataChanged();
1312                    }
1313    
1314                    public void treeCollapsed(TreeExpansionEvent event) {
1315                        fireTableDataChanged();
1316                    }
1317                });
1318            }
1319    
1320            /**
1321             * 
1322             * @param model must not be null!
1323             */
1324            public void setTreeTableModel(TreeTableModel model) {
1325                TreeTableModel old = getTreeTableModel();
1326                if (old != null) {
1327                    old.removeTreeModelListener(getTreeModelListener());
1328                }
1329                this.model = model;
1330                // Install a TreeModelListener that can update the table when
1331                // tree changes. 
1332                model.addTreeModelListener(getTreeModelListener());
1333                fireTableStructureChanged();
1334            }
1335    
1336            /**
1337             * @return <code>TreeModelListener</code>
1338             */
1339            private TreeModelListener getTreeModelListener() {
1340                if (treeModelListener == null) {
1341                    treeModelListener = new TreeModelListener() {
1342                        // We use delayedFireTableDataChanged as we can
1343                        // not be guaranteed the tree will have finished processing
1344                        // the event before us.
1345                        public void treeNodesChanged(TreeModelEvent e) {
1346                            delayedFireTableDataChanged(e, 0);
1347                        }
1348    
1349                        public void treeNodesInserted(TreeModelEvent e) {
1350                            delayedFireTableDataChanged(e, 1);
1351                        }
1352    
1353                        public void treeNodesRemoved(TreeModelEvent e) {
1354                            delayedFireTableDataChanged(e, 2);
1355                        }
1356    
1357                        public void treeStructureChanged(TreeModelEvent e) {
1358                            delayedFireTableDataChanged();
1359                        }
1360                    };
1361                }
1362                return treeModelListener;
1363            }
1364    
1365            /**
1366             * Returns the real TreeTableModel that is wrapped by this TreeTableModelAdapter.
1367             *
1368             * @return the real TreeTableModel that is wrapped by this TreeTableModelAdapter
1369             */
1370            public TreeTableModel getTreeTableModel() {
1371                return model;
1372            }
1373    
1374            /**
1375             * Returns the JXTreeTable instance to which this TreeTableModelAdapter is
1376             * permanently and exclusively bound. For use by
1377             * {@link org.jdesktop.swingx.JXTreeTable#setModel(javax.swing.table.TableModel)}.
1378             *
1379             * @return JXTreeTable to which this TreeTableModelAdapter is permanently bound
1380             */
1381            protected JXTreeTable getTreeTable() {
1382                return treeTable;
1383            }
1384    
1385            /**
1386             * Immutably binds this TreeTableModelAdapter to the specified JXTreeTable.
1387             *
1388             * @param treeTable the JXTreeTable instance that this adapter is bound to.
1389             */
1390            protected final void bind(JXTreeTable treeTable) {
1391                // Suppress potentially subversive invocation!
1392                // Prevent clearing out the deck for possible hijack attempt later!
1393                if (treeTable == null) {
1394                    throw new IllegalArgumentException("null treeTable");
1395                }
1396    
1397                if (this.treeTable == null) {
1398                    this.treeTable = treeTable;
1399                }
1400                else {
1401                    throw new IllegalArgumentException("adapter already bound");
1402                }
1403            }
1404    
1405            // Wrappers, implementing TableModel interface.
1406            // TableModelListener management provided by AbstractTableModel superclass.
1407    
1408            @Override
1409            public Class getColumnClass(int column) {
1410                return model.getColumnClass(column);
1411            }
1412    
1413            public int getColumnCount() {
1414                return model.getColumnCount();
1415            }
1416    
1417            @Override
1418            public String getColumnName(int column) {
1419                return model.getColumnName(column);
1420            }
1421    
1422            public int getRowCount() {
1423                return tree.getRowCount();
1424            }
1425    
1426            public Object getValueAt(int row, int column) {
1427                return model.getValueAt(nodeForRow(row), column);
1428            }
1429    
1430            @Override
1431            public boolean isCellEditable(int row, int column) {
1432                return model.isCellEditable(nodeForRow(row), column);
1433            }
1434    
1435            @Override
1436            public void setValueAt(Object value, int row, int column) {
1437                model.setValueAt(value, nodeForRow(row), column);
1438            }
1439    
1440            protected Object nodeForRow(int row) {
1441                return tree.getPathForRow(row).getLastPathComponent();
1442            }
1443    
1444            /**
1445             * Invokes fireTableDataChanged after all the pending events have been
1446             * processed. SwingUtilities.invokeLater is used to handle this.
1447             */
1448            private void delayedFireTableDataChanged() {
1449                SwingUtilities.invokeLater(new Runnable() {
1450                    public void run() {
1451                        fireTableDataChanged();
1452                    }
1453                });
1454            }
1455    
1456            /**
1457             * Invokes fireTableDataChanged after all the pending events have been
1458             * processed. SwingUtilities.invokeLater is used to handle this.
1459             */
1460            private void delayedFireTableDataChanged(final TreeModelEvent tme, final int typeChange) {
1461                SwingUtilities.invokeLater(new Runnable() {
1462                    public void run() {
1463                        int indices[] = tme.getChildIndices();
1464                        TreePath path = tme.getTreePath();
1465                        if (indices != null) { 
1466                            if (tree.isExpanded(path)) { // Dont bother to update if the parent 
1467                                                        // node is collapsed
1468                                int startingRow = tree.getRowForPath(path)+1;
1469                                int min = Integer.MAX_VALUE;
1470                                int max = Integer.MIN_VALUE;
1471                                for (int i=0;i<indices.length;i++) {
1472                                    if (indices[i] < min) {
1473                                        min = indices[i];
1474                                    }
1475                                    if (indices[i] > max) {
1476                                        max = indices[i];
1477                                    }
1478                                }
1479                                switch (typeChange) {
1480                                    case 0 :
1481                                        fireTableRowsUpdated(startingRow + min, startingRow+max);
1482                                    break;
1483                                    case 1: 
1484                                        fireTableRowsInserted(startingRow + min, startingRow+max);
1485                                    break;
1486                                    case 2:
1487                                        fireTableRowsDeleted(startingRow + min, startingRow+max);
1488                                    break;
1489                                }
1490                            } else { 
1491                            // not expanded - but change might effect appearance of parent
1492                            // Issue #82-swingx
1493                                int row = tree.getRowForPath(path);
1494                                // fix Issue #247-swingx: prevent accidental structureChanged
1495                                // for collapsed path 
1496                                // in this case row == -1, which == TableEvent.HEADER_ROW
1497                                if (row >= 0) fireTableRowsUpdated(row, row);
1498                            }
1499                        }
1500                        else {  // case where the event is fired to identify root.
1501                            fireTableDataChanged();
1502                        }
1503                    }
1504                });
1505            }
1506    
1507    
1508    
1509            private TreeTableModel model; // immutable
1510            private final JTree tree; // immutable
1511            private JXTreeTable treeTable = null; // logically immutable
1512        }
1513    
1514        static class TreeTableCellRenderer extends JXTree implements
1515            TableCellRenderer {
1516            // Force user to specify TreeTableModel instead of more general TreeModel
1517            public TreeTableCellRenderer(TreeTableModel model) {
1518                super(model);
1519                putClientProperty("JTree.lineStyle", "None");
1520                setRootVisible(false); // superclass default is "true"
1521                setShowsRootHandles(true); // superclass default is "false"
1522                    /** TODO: Support truncated text directly in DefaultTreeCellRenderer. */
1523                setOverwriteRendererIcons(true);
1524                setCellRenderer(new ClippedTreeCellRenderer());
1525            }
1526    
1527            /**
1528             * Hack around #297-swingx: tooltips shown at wrong row.
1529             * 
1530             * The problem is that - due to much tricksery when rendering
1531             * the tree - the given coordinates are rather useless. As a 
1532             * consequence, super maps to wrong coordinates. This takes
1533             * over completely.
1534             * 
1535             * PENDING: bidi?
1536             * 
1537             * @param event the mouseEvent in treetable coordinates
1538             * @param row the view row index
1539             * @param column the view column index
1540             * @return the tooltip as appropriate for the given row
1541             */
1542            private String getToolTipText(MouseEvent event, int row, int column) {
1543                if (row < 0) return null;
1544                TreeCellRenderer renderer = getCellRenderer();
1545                TreePath     path = getPathForRow(row);
1546                Object       lastPath = path.getLastPathComponent();
1547                Component    rComponent = renderer.getTreeCellRendererComponent
1548                    (this, lastPath, isRowSelected(row),
1549                     isExpanded(row), getModel().isLeaf(lastPath), row,
1550                     true);
1551    
1552                if(rComponent instanceof JComponent) {
1553                    Rectangle       pathBounds = getPathBounds(path);
1554                    Rectangle cellRect = treeTable.getCellRect(row, column, false);
1555                    // JW: what we are after
1556                    // is the offset into the hierarchical column 
1557                    // then intersect this with the pathbounds   
1558                    Point mousePoint = event.getPoint();
1559                    // translate to coordinates relative to cell
1560                    mousePoint.translate(-cellRect.x, -cellRect.y);
1561                    // translate horizontally to 
1562                    mousePoint.translate(-pathBounds.x, 0);
1563                    // show tooltip only if over renderer?
1564    //                if (mousePoint.x < 0) return null;
1565    //                p.translate(-pathBounds.x, -pathBounds.y);
1566                    MouseEvent newEvent = new MouseEvent(rComponent, event.getID(),
1567                          event.getWhen(),
1568                          event.getModifiers(),
1569                          mousePoint.x, 
1570                          mousePoint.y,
1571    //                    p.x, p.y, 
1572                          event.getClickCount(),
1573                          event.isPopupTrigger());
1574                    
1575                    return ((JComponent)rComponent).getToolTipText(newEvent);
1576                }
1577    
1578                return null;
1579            }
1580    
1581            /**
1582             * Immutably binds this TreeTableModelAdapter to the specified JXTreeTable.
1583             * For internal use by JXTreeTable only.
1584             *
1585             * @param treeTable the JXTreeTable instance that this renderer is bound to
1586             */
1587            public final void bind(JXTreeTable treeTable) {
1588                // Suppress potentially subversive invocation!
1589                // Prevent clearing out the deck for possible hijack attempt later!
1590                if (treeTable == null) {
1591                    throw new IllegalArgumentException("null treeTable");
1592                }
1593    
1594                if (this.treeTable == null) {
1595                    this.treeTable = treeTable;
1596                }
1597                else {
1598                    throw new IllegalArgumentException("renderer already bound");
1599                }
1600            }
1601    
1602            /**
1603             * updateUI is overridden to set the colors of the Tree's renderer
1604             * to match that of the table.
1605             */
1606            @Override
1607            public void updateUI() {
1608                super.updateUI();
1609                // Make the tree's cell renderer use the table's cell selection
1610                // colors.
1611                // TODO JW: need to revisit...
1612                // a) the "real" of a JXTree is always wrapped into a DelegatingRenderer
1613                //  consequently the if-block never executes
1614                // b) even if it does it probably (?) should not 
1615                // unconditionally overwrite custom selection colors. 
1616                // Check for UIResources instead. 
1617                TreeCellRenderer tcr = getCellRenderer();
1618                if (tcr instanceof DefaultTreeCellRenderer) {
1619                    DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer) tcr);
1620                    // For 1.1 uncomment this, 1.2 has a bug that will cause an
1621                    // exception to be thrown if the border selection color is null.
1622                    dtcr.setBorderSelectionColor(null);
1623                    dtcr.setTextSelectionColor(
1624                        UIManager.getColor("Table.selectionForeground"));
1625                    dtcr.setBackgroundSelectionColor(
1626                        UIManager.getColor("Table.selectionBackground"));
1627                }
1628            }
1629    
1630            /**
1631             * Sets the row height of the tree, and forwards the row height to
1632             * the table.
1633             */
1634            @Override
1635            public void setRowHeight(int rowHeight) {
1636                super.setRowHeight(rowHeight);
1637                if (rowHeight > 0) {
1638                    // JW: setting the largeModel property is suggested in
1639                    // #25-swingx. 
1640                    // backing out: leads to NPEs and icons not showing
1641    //                setLargeModel(true);
1642                    if (treeTable != null) {
1643                        // Reconcile semantic differences between JTable and JTree
1644                        final int tableRowMargin = treeTable.getRowMargin();
1645                        assert tableRowMargin >= 0;
1646                        final int tableRowHeight = rowHeight - (tableRowMargin << 1);
1647                        if (treeTable.getRowHeight() != tableRowHeight) {
1648                            treeTable.adminSetRowHeight(tableRowHeight);
1649                        }
1650                    }
1651                } 
1652    //            else {
1653    //                setLargeModel(false);
1654    //            }
1655            }
1656    
1657            /**
1658             * This is overridden to set the height to match that of the JTable.
1659             */
1660            @Override
1661            public void setBounds(int x, int y, int w, int h) {
1662                if (treeTable != null) {
1663                    y = 0;
1664                    // It is not enough to set the height to treeTable.getHeight()
1665                    h = treeTable.getRowCount() * this.getRowHeight();
1666                }
1667                super.setBounds(x, y, w, h);
1668            }
1669    
1670            /**
1671             * Sublcassed to translate the graphics such that the last visible row
1672             * will be drawn at 0,0.
1673             */
1674            @Override
1675            public void paint(Graphics g) {
1676                int rowMargin = treeTable.getRowMargin();
1677                // MUST account for rowMargin for precise positioning.
1678                // Offset by (rowMargin * 3)/2, and remember to offset by an
1679                // additional pixel if rowMargin is odd!
1680                int margins = rowMargin + (rowMargin >> 1) + (rowMargin % 2);
1681                int translationOffset = margins + visibleRow * getRowHeight();
1682                g.translate(0, -translationOffset);
1683    
1684                hierarchicalColumnWidth = getWidth();
1685                super.paint(g);
1686    
1687                // Draw the Table border if we have focus.
1688                if (highlightBorder != null) {
1689                    // #170: border not drawn correctly
1690                    // JW: position the border to be drawn in translated area
1691                    // still not satifying in all cases...
1692                    // RG: Now it satisfies (at least for the row margins)
1693                    // Still need to make similar adjustments for column margins...
1694                    highlightBorder.paintBorder(this, g, 0, translationOffset,
1695                            getWidth(),
1696                            // uhhh getRowHeight() + 1 - 2 * (margins)
1697                            getRowHeight() - (rowMargin << 1) - rowMargin); // RG:
1698                                                                            // subtract
1699                                                                            // (rowMargin
1700                                                                            // * 3)
1701                }
1702            }
1703    
1704            public Component getTableCellRendererComponent(JTable table,
1705                Object value,
1706                boolean isSelected, boolean hasFocus, int row, int column) {
1707                assert table == treeTable;
1708    
1709                if (isSelected) {
1710                    setBackground(table.getSelectionBackground());
1711                    setForeground(table.getSelectionForeground());
1712                }
1713                else {
1714                    setBackground(table.getBackground());
1715                   setForeground(table.getForeground());
1716                }
1717    
1718                highlightBorder = null;
1719                if (treeTable != null) {
1720                    if (treeTable.realEditingRow() == row &&
1721                        treeTable.getEditingColumn() == column) {
1722                    }
1723                    else if (hasFocus) {
1724                        highlightBorder = UIManager.getBorder(
1725                            "Table.focusCellHighlightBorder");
1726                    }
1727                }
1728    
1729                
1730                visibleRow = row;
1731    
1732                return this;
1733            }
1734    
1735            private class ClippedTreeCellRenderer extends DefaultTreeCellRenderer {
1736                public void paint(Graphics g) {
1737                    String fullText = super.getText();
1738                    // getText() calls tree.convertValueToText();
1739                    // tree.convertValueToText() should call treeModel.convertValueToText(), if possible
1740    
1741                    String shortText = SwingUtilities.layoutCompoundLabel(
1742                        this, g.getFontMetrics(), fullText, getIcon(),
1743                        getVerticalAlignment(), getHorizontalAlignment(),
1744                        getVerticalTextPosition(), getHorizontalTextPosition(),
1745                        getItemRect(itemRect), iconRect, textRect,
1746                        getIconTextGap());
1747    
1748                    /** TODO: setText is more heavyweight than we want in this
1749                     * situation. Make JLabel.text protected instead of private.
1750                     */
1751    
1752                    setText(shortText); // temporarily truncate text
1753                    super.paint(g);
1754                    setText(fullText); // restore full text
1755                }
1756    
1757                private Rectangle getItemRect(Rectangle itemRect) {
1758                    getBounds(itemRect);
1759                    itemRect.width = hierarchicalColumnWidth - itemRect.x;
1760                    return itemRect;
1761                }
1762    
1763                // Rectangles filled in by SwingUtilities.layoutCompoundLabel();
1764                private final Rectangle iconRect = new Rectangle();
1765                private final Rectangle textRect = new Rectangle();
1766                // Rectangle filled in by this.getItemRect();
1767                private final Rectangle itemRect = new Rectangle();
1768            }
1769    
1770            /** Border to draw around the tree, if this is non-null, it will
1771             * be painted. */
1772            protected Border highlightBorder = null;
1773            protected JXTreeTable treeTable = null;
1774            protected int visibleRow = 0;
1775    
1776            // A JXTreeTable may not have more than one hierarchical column
1777            private int hierarchicalColumnWidth = 0;
1778        }
1779    
1780        /**
1781         * Returns the adapter that knows how to access the component data model.
1782         * The component data adapter is used by filters, sorters, and highlighters.
1783         *
1784         * @return the adapter that knows how to access the component data model
1785         */
1786        @Override
1787        protected ComponentAdapter getComponentAdapter() {
1788            if (dataAdapter == null) {
1789                dataAdapter = new TreeTableDataAdapter(this); 
1790            }
1791            // MUST ALWAYS ACCESS dataAdapter through accessor method!!!
1792            return dataAdapter;
1793        }
1794    
1795    
1796        private final static Dimension spacing = new Dimension(0, 2);
1797    
1798        protected static class TreeTableDataAdapter extends JXTable.TableAdapter {
1799            private final JXTreeTable table;
1800    
1801            /**
1802             * Constructs a <code>TreeTableDataAdapter</code> for the specified
1803             * target component.
1804             *
1805             * @param component the target component
1806             */
1807            public TreeTableDataAdapter(JXTreeTable component) {
1808                super(component);
1809                table = component;
1810            }
1811            public JXTreeTable getTreeTable() {
1812                return table;
1813            }
1814    
1815            @Override
1816            public boolean isExpanded() {
1817                return super.isExpanded(); /** TODO: implement this method */
1818            }
1819    
1820            @Override
1821            public boolean hasFocus() {
1822                boolean focus = super.hasFocus(); /** TODO: implement this method */
1823                return focus;
1824            }
1825    
1826            @Override
1827            public boolean isLeaf() {
1828                return super.isLeaf(); /** TODO: implement this method */
1829            }
1830            /**
1831             *
1832             * @return true if the cell identified by this adapter displays hierarchical
1833             *      nodes; false otherwise
1834             */
1835            @Override
1836            public boolean isHierarchical() {
1837                return table.isHierarchical(column);
1838            }
1839        }
1840    
1841    }