001    /*
002     * $Id: JXTreeTable.java 3387 2009-07-09 12:04:57Z kleopatra $
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    
025    import java.awt.Color;
026    import java.awt.Component;
027    import java.awt.Graphics;
028    import java.awt.Point;
029    import java.awt.Rectangle;
030    import java.awt.event.ActionEvent;
031    import java.awt.event.InputEvent;
032    import java.awt.event.MouseEvent;
033    import java.beans.PropertyChangeEvent;
034    import java.beans.PropertyChangeListener;
035    import java.util.ArrayList;
036    import java.util.Enumeration;
037    import java.util.EventObject;
038    import java.util.List;
039    import java.util.logging.Logger;
040    
041    import javax.swing.ActionMap;
042    import javax.swing.Icon;
043    import javax.swing.JComponent;
044    import javax.swing.JTable;
045    import javax.swing.JTree;
046    import javax.swing.ListSelectionModel;
047    import javax.swing.RowSorter;
048    import javax.swing.SwingUtilities;
049    import javax.swing.UIManager;
050    import javax.swing.border.Border;
051    import javax.swing.event.ListSelectionEvent;
052    import javax.swing.event.ListSelectionListener;
053    import javax.swing.event.TableModelEvent;
054    import javax.swing.event.TreeExpansionEvent;
055    import javax.swing.event.TreeExpansionListener;
056    import javax.swing.event.TreeModelEvent;
057    import javax.swing.event.TreeModelListener;
058    import javax.swing.event.TreeSelectionListener;
059    import javax.swing.event.TreeWillExpandListener;
060    import javax.swing.table.AbstractTableModel;
061    import javax.swing.table.TableCellEditor;
062    import javax.swing.table.TableCellRenderer;
063    import javax.swing.table.TableModel;
064    import javax.swing.tree.DefaultTreeCellRenderer;
065    import javax.swing.tree.DefaultTreeSelectionModel;
066    import javax.swing.tree.TreeCellRenderer;
067    import javax.swing.tree.TreePath;
068    import javax.swing.tree.TreeSelectionModel;
069    
070    import org.jdesktop.swingx.decorator.ComponentAdapter;
071    import org.jdesktop.swingx.renderer.StringValue;
072    import org.jdesktop.swingx.renderer.StringValues;
073    import org.jdesktop.swingx.rollover.RolloverProducer;
074    import org.jdesktop.swingx.rollover.RolloverRenderer;
075    import org.jdesktop.swingx.tree.DefaultXTreeCellRenderer;
076    import org.jdesktop.swingx.treetable.DefaultTreeTableModel;
077    import org.jdesktop.swingx.treetable.TreeTableCellEditor;
078    import org.jdesktop.swingx.treetable.TreeTableModel;
079    
080    /**
081     * <p><code>JXTreeTable</code> is a specialized {@link javax.swing.JTable table}
082     * consisting of a single column in which to display hierarchical data, and any
083     * number of other columns in which to display regular data. The interface for
084     * the data model used by a <code>JXTreeTable</code> is
085     * {@link org.jdesktop.swingx.treetable.TreeTableModel}. It extends the
086     * {@link javax.swing.tree.TreeModel} interface to allow access to cell data by
087     * column indices within each node of the tree hierarchy.</p>
088     *
089     * <p>The most straightforward way create and use a <code>JXTreeTable</code>, is to
090     * first create a suitable data model for it, and pass that to a
091     * <code>JXTreeTable</code> constructor, as shown below:
092     * <pre>
093     *  TreeTableModel  treeTableModel = new FileSystemModel(); // any TreeTableModel
094     *  JXTreeTable     treeTable = new JXTreeTable(treeTableModel);
095     *  JScrollPane     scrollpane = new JScrollPane(treeTable);
096     * </pre>
097     * See {@link javax.swing.JTable} for an explanation of why putting the treetable
098     * inside a scroll pane is necessary.</p>
099     *
100     * <p>A single treetable model instance may be shared among more than one
101     * <code>JXTreeTable</code> instances. To access the treetable model, always call
102     * {@link #getTreeTableModel() getTreeTableModel} and
103     * {@link #setTreeTableModel(org.jdesktop.swingx.treetable.TreeTableModel) setTreeTableModel}.
104     * <code>JXTreeTable</code> wraps the supplied treetable model inside a private
105     * adapter class to adapt it to a {@link javax.swing.table.TableModel}. Although
106     * the model adapter is accessible through the {@link #getModel() getModel} method, you
107     * should avoid accessing and manipulating it in any way. In particular, each
108     * model adapter instance is tightly bound to a single table instance, and any
109     * attempt to share it with another table (for example, by calling
110     * {@link #setModel(javax.swing.table.TableModel) setModel})
111     * will throw an <code>IllegalArgumentException</code>!
112     *
113     * @author Philip Milne
114     * @author Scott Violet
115     * @author Ramesh Gupta
116     */
117    public class JXTreeTable extends JXTable {
118        @SuppressWarnings("unused")
119        private static final Logger LOG = Logger.getLogger(JXTreeTable.class
120                .getName());
121        /**
122         * Key for clientProperty to decide whether to apply hack around #168-jdnc.
123         */
124        public static final String DRAG_HACK_FLAG_KEY = "treeTable.dragHackFlag";
125        /**
126         * Key for clientProperty to decide whether to apply hack around #766-swingx.
127         */
128        public static final String DROP_HACK_FLAG_KEY = "treeTable.dropHackFlag";
129        /**
130         * Renderer used to render cells within the
131         *  {@link #isHierarchical(int) hierarchical} column.
132         *  renderer extends JXTree and implements TableCellRenderer
133         */
134        private TreeTableCellRenderer renderer;
135    
136        /**
137         * Editor used to edit cells within the
138         *  {@link #isHierarchical(int) hierarchical} column.
139         */
140        private TreeTableCellEditor hierarchicalEditor;
141        
142        private TreeTableHacker treeTableHacker;
143        private boolean consumedOnPress;
144    
145        /**
146         * Constructs a JXTreeTable using a
147         * {@link org.jdesktop.swingx.treetable.DefaultTreeTableModel}.
148         */
149        public JXTreeTable() {
150            this(new DefaultTreeTableModel());
151        }
152    
153        /**
154         * Constructs a JXTreeTable using the specified
155         * {@link org.jdesktop.swingx.treetable.TreeTableModel}.
156         *
157         * @param treeModel model for the JXTreeTable
158         */
159        public JXTreeTable(TreeTableModel treeModel) {
160            this(new JXTreeTable.TreeTableCellRenderer(treeModel));
161        }
162    
163        /**
164         * Constructs a <code>JXTreeTable</code> using the specified
165         * {@link org.jdesktop.swingx.JXTreeTable.TreeTableCellRenderer}.
166         * 
167         * @param renderer
168         *                cell renderer for the tree portion of this JXTreeTable
169         *                instance.
170         */
171        private JXTreeTable(TreeTableCellRenderer renderer) {
172            // To avoid unnecessary object creation, such as the construction of a
173            // DefaultTableModel, it is better to invoke
174            // super(TreeTableModelAdapter) directly, instead of first invoking
175            // super() followed by a call to setTreeTableModel(TreeTableModel).
176    
177            // Adapt tree model to table model before invoking super()
178            super(new TreeTableModelAdapter(renderer));
179    
180            // renderer-related initialization
181            init(renderer); // private method
182            initActions();
183            // disable sorting
184            super.setSortable(false);
185            super.setAutoCreateRowSorter(false);
186            super.setRowSorter(null);
187            // no grid
188            setShowGrid(false, false);
189    
190            hierarchicalEditor = new TreeTableCellEditor(renderer);
191            
192    //        // No grid.
193    //        setShowGrid(false); // superclass default is "true"
194    //
195    //        // Default intercell spacing
196    //        setIntercellSpacing(spacing); // for both row margin and column margin
197    
198        }
199    
200        /**
201         * Initializes this JXTreeTable and permanently binds the specified renderer
202         * to it.
203         *
204         * @param renderer private tree/renderer permanently and exclusively bound
205         * to this JXTreeTable.
206         */
207        private void init(TreeTableCellRenderer renderer) {
208            this.renderer = renderer;
209            assert ((TreeTableModelAdapter) getModel()).tree == this.renderer;
210            
211            // Force the JTable and JTree to share their row selection models.
212            ListToTreeSelectionModelWrapper selectionWrapper =
213                new ListToTreeSelectionModelWrapper();
214    
215            // JW: when would that happen?
216            if (renderer != null) {
217                renderer.bind(this); // IMPORTANT: link back!
218                renderer.setSelectionModel(selectionWrapper);
219            }
220            // adjust the tree's rowHeight to this.rowHeight
221            adjustTreeRowHeight(getRowHeight());
222    
223            setSelectionModel(selectionWrapper.getListSelectionModel());
224            
225            // propagate the lineStyle property to the renderer
226            PropertyChangeListener l = new PropertyChangeListener() {
227    
228                public void propertyChange(PropertyChangeEvent evt) {
229                    JXTreeTable.this.renderer.putClientProperty(evt.getPropertyName(), evt.getNewValue());
230                    
231                }
232                
233            };
234            addPropertyChangeListener("JTree.lineStyle", l);
235            
236        }
237    
238    
239        
240        private void initActions() {
241            // Register the actions that this class can handle.
242            ActionMap map = getActionMap();
243            map.put("expand-all", new Actions("expand-all"));
244            map.put("collapse-all", new Actions("collapse-all"));
245        }
246    
247        /**
248         * A small class which dispatches actions.
249         * TODO: Is there a way that we can make this static?
250         */
251        private class Actions extends UIAction {
252            Actions(String name) {
253                super(name);
254            }
255    
256            public void actionPerformed(ActionEvent evt) {
257                if ("expand-all".equals(getName())) {
258            expandAll();
259                }
260                else if ("collapse-all".equals(getName())) {
261                    collapseAll();
262                }
263            }
264        }
265    
266        /** 
267         * {@inheritDoc} <p>
268         * Overridden to do nothing. 
269         * 
270         * TreeTable is not sortable because there is no equivalent to 
271         * RowSorter (which is targeted to linear structures) for 
272         * hierarchical data.
273         * 
274         */
275        @Override
276        public void setSortable(boolean sortable) {
277            // no-op
278        }
279    
280        
281        
282        /** 
283         * {@inheritDoc} <p>
284         * Overridden to do nothing. 
285         * 
286         * TreeTable is not sortable because there is no equivalent to 
287         * RowSorter (which is targeted to linear structures) for 
288         * hierarchical data.
289         * 
290         */
291        @Override
292        public void setAutoCreateRowSorter(boolean autoCreateRowSorter) {
293        }
294    
295        /** 
296         * {@inheritDoc} <p>
297         * Overridden to do nothing. 
298         * 
299         * TreeTable is not sortable because there is no equivalent to 
300         * RowSorter (which is targeted to linear structures) for 
301         * hierarchical data.
302         * 
303         */
304        @Override
305        public void setRowSorter(RowSorter<? extends TableModel> sorter) {
306        }
307    
308        /**
309         * {@inheritDoc} <p>
310         * 
311         * Overridden to keep the tree's enabled in synch.
312         */
313        @Override
314        public void setEnabled(boolean enabled) {
315            renderer.setEnabled(enabled);
316            super.setEnabled(enabled);
317        }
318        
319    
320        /**
321         * {@inheritDoc} <p>
322         * 
323         * Overridden to keep the tree's selectionBackground in synch.
324         */
325        @Override
326        public void setSelectionBackground(Color selectionBackground) {
327            // happens on instantiation, updateUI is called before the renderer is installed
328            if (renderer != null)
329                renderer.setSelectionBackground(selectionBackground);
330            super.setSelectionBackground(selectionBackground);
331        }
332    
333        /**
334         * {@inheritDoc} <p>
335         * 
336         * Overridden to keep the tree's selectionForeground in synch.
337         */
338        @Override
339        public void setSelectionForeground(Color selectionForeground) {
340            // happens on instantiation, updateUI is called before the renderer is installed
341            if (renderer != null)
342                renderer.setSelectionForeground(selectionForeground);
343            super.setSelectionForeground(selectionForeground);
344        }
345    
346        /**
347         * Overriden to invoke repaint for the particular location if
348         * the column contains the tree. This is done as the tree editor does
349         * not fill the bounds of the cell, we need the renderer to paint
350         * the tree in the background, and then draw the editor over it.
351         * You should not need to call this method directly. <p>
352         * 
353         * Additionally, there is tricksery involved to expand/collapse
354         * the nodes.
355         *
356         * {@inheritDoc}
357         */
358        @Override
359        public boolean editCellAt(int row, int column, EventObject e) {
360            getTreeTableHacker().hitHandleDetectionFromEditCell(column, e);    // RG: Fix Issue 49!
361            boolean canEdit = super.editCellAt(row, column, e);
362            if (canEdit && isHierarchical(column)) {
363                repaint(getCellRect(row, column, false));
364            }
365            return canEdit;
366        }
367    
368        /**
369         * Overridden to enable hit handle detection a mouseEvent which triggered
370         * a expand/collapse. 
371         */
372        @Override
373        protected void processMouseEvent(MouseEvent e) {
374            // BasicTableUI selects on released if the pressed had been 
375            // consumed. So we try to fish for the accompanying released
376            // here and consume it as wll. 
377            if ((e.getID() == MouseEvent.MOUSE_RELEASED) && consumedOnPress) {
378                consumedOnPress = false;
379                e.consume();
380                return;
381            }
382            if (getTreeTableHacker().hitHandleDetectionFromProcessMouse(e)) {
383                // Issue #332-swing: hacking around selection loss.
384                // prevent the
385                // _table_ selection by consuming the mouseEvent
386                // if it resulted in a expand/collapse
387                consumedOnPress = true;
388                e.consume();
389                return;
390            }
391            consumedOnPress = false;
392            super.processMouseEvent(e);
393        }
394        
395    
396        protected TreeTableHacker getTreeTableHacker() {
397            if (treeTableHacker == null) {
398                treeTableHacker = createTreeTableHacker();
399            }
400            return treeTableHacker;
401        }
402        
403        protected TreeTableHacker createTreeTableHacker() {
404    //        return new TreeTableHacker();
405            return new TreeTableHackerExt();
406    //        return new TreeTableHackerExt2();
407        }
408    
409        /**
410         * Temporary class to have all the hacking at one place. Naturally, it will
411         * change a lot. The base class has the "stable" behaviour as of around
412         * jun2006 (before starting the fix for 332-swingx). <p>
413         * 
414         * specifically:
415         * 
416         * <ol>
417         * <li> hitHandleDetection triggeredn in editCellAt
418         * </ol>
419         * 
420         */
421        public class TreeTableHacker {
422    
423            protected boolean expansionChangedFlag;
424    
425            /**
426             * Decision whether the handle hit detection
427             *   should be done in processMouseEvent or editCellAt.
428             * Here: returns false.
429             * 
430             * @return true for handle hit detection in processMouse, false
431             *   for editCellAt.
432             */
433            protected boolean isHitDetectionFromProcessMouse() {
434                return false;
435            }
436    
437            /**
438            * Entry point for hit handle detection called from editCellAt, 
439            * does nothing if isHitDetectionFromProcessMouse is true;
440            * 
441            * @see #isHitDetectionFromProcessMouse()
442            */
443            public void hitHandleDetectionFromEditCell(int column, EventObject e) {
444                if (!isHitDetectionFromProcessMouse()) {
445                    expandOrCollapseNode(column, e);
446                }
447            }
448    
449            /**
450             * Entry point for hit handle detection called from processMouse.
451             * Does nothing if isHitDetectionFromProcessMouse is false. 
452             * 
453             * @return true if the mouseEvent triggered an expand/collapse in
454             *   the renderer, false otherwise. 
455             *   
456             * @see #isHitDetectionFromProcessMouse()
457             */
458            public boolean hitHandleDetectionFromProcessMouse(MouseEvent e) {
459                if (!isHitDetectionFromProcessMouse())
460                    return false;
461                int col = columnAtPoint(e.getPoint());
462                return ((col >= 0) && expandOrCollapseNode(columnAtPoint(e
463                        .getPoint()), e));
464            }
465    
466            /**
467             * Complete editing if collapsed/expanded.
468             * <p>
469             * 
470             * Is: first try to stop editing before falling back to cancel.
471             * <p>
472             * This is part of fix for #730-swingx - editingStopped not always
473             * called. The other part is to call this from the renderer before
474             * expansion related state has changed.
475             * <p>
476             * 
477             * Was: any editing is always cancelled.
478             * <p>
479             * This is a rude fix to #120-jdnc: data corruption on collapse/expand
480             * if editing. This is called from the renderer after expansion related
481             * state has changed.
482             * 
483             */
484            protected void completeEditing() {
485                if (isEditing()) {
486                    boolean success = getCellEditor().stopCellEditing();
487                    if (!success) {
488                        getCellEditor().cancelCellEditing();
489                    }
490                }
491            }
492    
493            /**
494             * Tricksery to make the tree expand/collapse.
495             * <p>
496             * 
497             * This might be - indirectly - called from one of two places:
498             * <ol>
499             * <li> editCellAt: original, stable but buggy (#332, #222) the table's
500             * own selection had been changed due to the click before even entering
501             * into editCellAt so all tree selection state is lost.
502             * 
503             * <li> processMouseEvent: the idea is to catch the mouseEvent, check
504             * if it triggered an expanded/collapsed, consume and return if so or 
505             * pass to super if not.
506             * </ol>
507             * 
508             * <p>
509             * widened access for testing ...
510             * 
511             * 
512             * @param column the column index under the event, if any.
513             * @param e the event which might trigger a expand/collapse.
514             * 
515             * @return this methods evaluation as to whether the event triggered a
516             *         expand/collaps
517             */
518            protected boolean expandOrCollapseNode(int column, EventObject e) {
519                if (!isHierarchical(column))
520                    return false;
521                if (!mightBeExpansionTrigger(e))
522                    return false;
523                boolean changedExpansion = false;
524                MouseEvent me = (MouseEvent) e;
525                if (hackAroundDragEnabled(me)) {
526                    /*
527                     * Hack around #168-jdnc: dirty little hack mentioned in the
528                     * forum discussion about the issue: fake a mousePressed if drag
529                     * enabled. The usability is slightly impaired because the
530                     * expand/collapse is effectively triggered on released only
531                     * (drag system intercepts and consumes all other).
532                     */
533                    me = new MouseEvent((Component) me.getSource(),
534                            MouseEvent.MOUSE_PRESSED, me.getWhen(), me
535                                    .getModifiers(), me.getX(), me.getY(), me
536                                    .getClickCount(), me.isPopupTrigger());
537    
538                }
539                // If the modifiers are not 0 (or the left mouse button),
540                // tree may try and toggle the selection, and table
541                // will then try and toggle, resulting in the
542                // selection remaining the same. To avoid this, we
543                // only dispatch when the modifiers are 0 (or the left mouse
544                // button).
545                if (me.getModifiers() == 0
546                        || me.getModifiers() == InputEvent.BUTTON1_MASK) {
547                    MouseEvent pressed = new MouseEvent(renderer, me.getID(), me
548                            .getWhen(), me.getModifiers(), me.getX()
549                            - getCellRect(0, column, false).x, me.getY(), me
550                            .getClickCount(), me.isPopupTrigger());
551                    renderer.dispatchEvent(pressed);
552                    // For Mac OS X, we need to dispatch a MOUSE_RELEASED as well
553                    MouseEvent released = new MouseEvent(renderer,
554                            java.awt.event.MouseEvent.MOUSE_RELEASED, pressed
555                                    .getWhen(), pressed.getModifiers(), pressed
556                                    .getX(), pressed.getY(), pressed
557                                    .getClickCount(), pressed.isPopupTrigger());
558                    renderer.dispatchEvent(released);
559                    if (expansionChangedFlag) {
560                        changedExpansion = true;
561                    }
562                }
563                expansionChangedFlag = false;
564                return changedExpansion;
565            }
566    
567            protected boolean mightBeExpansionTrigger(EventObject e) {
568                if (!(e instanceof MouseEvent)) return false;
569                MouseEvent me = (MouseEvent) e;
570                if (!SwingUtilities.isLeftMouseButton(me)) return false;
571                return me.getID() == MouseEvent.MOUSE_PRESSED;
572            }
573    
574            /**
575             * called from the renderer's setExpandedPath after
576             * all expansion-related updates happend.
577             *
578             */
579            protected void expansionChanged() {
580                expansionChangedFlag = true;
581            }
582    
583        }
584    
585        /**
586         * 
587         * Note: currently this class looks a bit funny (only overriding
588         * the hit decision method). That's because the "experimental" code
589         * as of the last round moved to stable. But I expect that there's more
590         * to come, so I leave it here.
591         * 
592         * <ol>
593         * <li> hit handle detection in processMouse
594         * </ol>
595         */
596        public class TreeTableHackerExt extends TreeTableHacker {
597    
598    
599            /**
600             * Here: returns true.
601             * @inheritDoc
602             */
603            @Override
604            protected boolean isHitDetectionFromProcessMouse() {
605                return true;
606            }
607    
608        }
609        
610        /**
611         * Patch for #471-swingx: no selection on click in hierarchical column
612         * outside of node-text. Mar 2007.
613         * <p>
614         * 
615         * Note: this solves the selection issue but is not bidi-compliant - in RToL
616         * contexts the expansion/collapse handles aren't detected and consequently
617         * are disfunctional.
618         * 
619         * @author tiberiu@dev.java.net
620         */
621        public class TreeTableHackerExt2 extends TreeTableHackerExt {
622            @Override
623            protected boolean expandOrCollapseNode(int column, EventObject e) {
624                if (!isHierarchical(column))
625                    return false;
626                if (!mightBeExpansionTrigger(e))
627                    return false;
628                boolean changedExpansion = false;
629                MouseEvent me = (MouseEvent) e;
630                if (hackAroundDragEnabled(me)) {
631                    /*
632                     * Hack around #168-jdnc: dirty little hack mentioned in the
633                     * forum discussion about the issue: fake a mousePressed if drag
634                     * enabled. The usability is slightly impaired because the
635                     * expand/collapse is effectively triggered on released only
636                     * (drag system intercepts and consumes all other).
637                     */
638                    me = new MouseEvent((Component) me.getSource(),
639                            MouseEvent.MOUSE_PRESSED, me.getWhen(), me
640                                    .getModifiers(), me.getX(), me.getY(), me
641                                    .getClickCount(), me.isPopupTrigger());
642                }
643                // If the modifiers are not 0 (or the left mouse button),
644                // tree may try and toggle the selection, and table
645                // will then try and toggle, resulting in the
646                // selection remaining the same. To avoid this, we
647                // only dispatch when the modifiers are 0 (or the left mouse
648                // button).
649                if (me.getModifiers() == 0
650                        || me.getModifiers() == InputEvent.BUTTON1_MASK) {
651                    // compute where the mouse point is relative to the tree
652                    // renderer
653                    Point treeMousePoint = getTreeMousePoint(column, me);
654                    int treeRow = renderer.getRowForLocation(treeMousePoint.x,
655                            treeMousePoint.y);
656                    int row = 0;
657                    if (treeRow < 0) {
658                        row = renderer.getClosestRowForLocation(treeMousePoint.x,
659                                treeMousePoint.y);
660                        Rectangle bounds = renderer.getRowBounds(row);
661                        if (bounds == null) {
662                            row = -1;
663                        } else {
664                            if ((bounds.y + bounds.height < treeMousePoint.y)
665                                    || bounds.x > treeMousePoint.x) {
666                                row = -1;
667                            }
668                        }
669                        // make sure the expansionChangedFlag is set to false for
670                        // the case that up in the tree nothing happens
671                        expansionChangedFlag = false;
672                    }
673    
674                    if ((treeRow >= 0) || ((treeRow < 0) && (row < 0))) {
675                        // default selection
676                        MouseEvent pressed = new MouseEvent(renderer, me.getID(),
677                                me.getWhen(), me.getModifiers(), treeMousePoint.x,
678                                treeMousePoint.y, me.getClickCount(), me
679                                        .isPopupTrigger());
680                        renderer.dispatchEvent(pressed);
681                        // For Mac OS X, we need to dispatch a MOUSE_RELEASED as
682                        // well
683                        MouseEvent released = new MouseEvent(renderer,
684                                java.awt.event.MouseEvent.MOUSE_RELEASED, pressed
685                                        .getWhen(), pressed.getModifiers(), pressed
686                                        .getX(), pressed.getY(), pressed
687                                        .getClickCount(), pressed.isPopupTrigger());
688                        renderer.dispatchEvent(released);
689                    }
690                    if (expansionChangedFlag) {
691                        changedExpansion = true;
692                    }
693                }
694                expansionChangedFlag = false;
695                return changedExpansion;
696            }
697    
698            /**
699             * This is a patch provided for Issue #980-swingx which should
700             * improve the bidi-compliance. Still doesn't work in our 
701             * visual tests...
702             * 
703             * @param column the column index under the event, if any.
704             * @param e the event which might trigger a expand/collapse.
705             * @return the Point adjusted for bidi
706             */
707            protected Point getTreeMousePoint(int column, MouseEvent me) {
708    //            return new Point(me.getX()
709    //                    - getCellRect(0, column, false).x, me.getY());
710                Rectangle tableCellRect = getCellRect(0, column, false);
711               
712                if( getComponentOrientation().isLeftToRight() ) {
713                    return new Point(me.getX() - tableCellRect.x, me.getY());
714                }
715     
716                int x = (me.getX() - tableCellRect.x) - tableCellRect.width - 10;
717                return new Point(x, me.getY());
718            }
719        }
720        
721        /**
722         * decides whether we want to apply the hack for #168-jdnc. here: returns
723         * true if dragEnabled() and the improved drag handling is not activated (or
724         * the system property is not accessible). The given mouseEvent is not
725         * analysed.
726         * 
727         * PENDING: Mustang?
728         * 
729         * @param me the mouseEvent that triggered a editCellAt
730         * @return true if the hack should be applied.
731         */
732        protected boolean hackAroundDragEnabled(MouseEvent me) {
733            Boolean dragHackFlag = (Boolean) getClientProperty(DRAG_HACK_FLAG_KEY);
734            if (dragHackFlag == null) {
735                // access and store the system property as a client property once
736                String priority = null;
737                try {
738                    priority = System.getProperty("sun.swing.enableImprovedDragGesture");
739    
740                } catch (Exception ex) {
741                    // found some foul expression or failed to read the property
742                }
743                dragHackFlag = (priority == null);
744                putClientProperty(DRAG_HACK_FLAG_KEY, dragHackFlag);
745            }
746            return getDragEnabled() && dragHackFlag;
747        }
748    
749        /**
750         * Overridden to provide a workaround for BasicTableUI anomaly. Make sure
751         * the UI never tries to resize the editor. The UI currently uses different
752         * techniques to paint the renderers and editors. So, overriding setBounds()
753         * is not the right thing to do for an editor. Returning -1 for the
754         * editing row in this case, ensures the editor is never painted.
755         *
756         * {@inheritDoc}
757         */
758        @Override
759        public int getEditingRow() {
760            return isHierarchical(editingColumn) ? -1 : editingRow;
761        }
762    
763        /**
764         * Returns the actual row that is editing as <code>getEditingRow</code>
765         * will always return -1.
766         */
767        private int realEditingRow() {
768            return editingRow;
769        }
770    
771        /**
772         * Sets the data model for this JXTreeTable to the specified
773         * {@link org.jdesktop.swingx.treetable.TreeTableModel}. The same data model
774         * may be shared by any number of JXTreeTable instances.
775         *
776         * @param treeModel data model for this JXTreeTable
777         */
778        public void setTreeTableModel(TreeTableModel treeModel) {
779            TreeTableModel old = getTreeTableModel();
780    //        boolean rootVisible = isRootVisible();
781    //        setRootVisible(false);
782            renderer.setModel(treeModel);
783    //        setRootVisible(rootVisible);
784            
785            firePropertyChange("treeTableModel", old, getTreeTableModel());
786        }
787    
788        /**
789         * Returns the underlying TreeTableModel for this JXTreeTable.
790         *
791         * @return the underlying TreeTableModel for this JXTreeTable
792         */
793        public TreeTableModel getTreeTableModel() {
794            return (TreeTableModel) renderer.getModel();
795        }
796    
797        /**
798         * <p>Overrides superclass version to make sure that the specified
799         * {@link javax.swing.table.TableModel} is compatible with JXTreeTable before
800         * invoking the inherited version.</p>
801         *
802         * <p>Because JXTreeTable internally adapts an
803         * {@link org.jdesktop.swingx.treetable.TreeTableModel} to make it a compatible
804         * TableModel, <b>this method should never be called directly</b>. Use
805         * {@link #setTreeTableModel(org.jdesktop.swingx.treetable.TreeTableModel) setTreeTableModel} instead.</p>
806         *
807         * <p>While it is possible to obtain a reference to this adapted
808         * version of the TableModel by calling {@link javax.swing.JTable#getModel()},
809         * any attempt to call setModel() with that adapter will fail because
810         * the adapter might have been bound to a different JXTreeTable instance. If
811         * you want to extract the underlying TreeTableModel, which, by the way,
812         * <em>can</em> be shared, use {@link #getTreeTableModel() getTreeTableModel}
813         * instead</p>.
814         *
815         * @param tableModel must be a TreeTableModelAdapter
816         * @throws IllegalArgumentException if the specified tableModel is not an
817         * instance of TreeTableModelAdapter
818         */
819        @Override
820        public final void setModel(TableModel tableModel) { // note final keyword
821            if (tableModel instanceof TreeTableModelAdapter) {
822                if (((TreeTableModelAdapter) tableModel).getTreeTable() == null) {
823                    // Passing the above test ensures that this method is being
824                    // invoked either from JXTreeTable/JTable constructor or from
825                    // setTreeTableModel(TreeTableModel)
826                    super.setModel(tableModel); // invoke superclass version
827    
828                    ((TreeTableModelAdapter) tableModel).bind(this); // permanently bound
829                    // Once a TreeTableModelAdapter is bound to any JXTreeTable instance,
830                    // invoking JXTreeTable.setModel() with that adapter will throw an
831                    // IllegalArgumentException, because we really want to make sure
832                    // that a TreeTableModelAdapter is NOT shared by another JXTreeTable.
833                }
834                else {
835                    throw new IllegalArgumentException("model already bound");
836                }
837            }
838            else {
839                throw new IllegalArgumentException("unsupported model type");
840            }
841        }
842    
843    
844        
845        @Override
846        public void tableChanged(TableModelEvent e) {
847            if (isStructureChanged(e) || isUpdate(e)) {
848                super.tableChanged(e);
849            } else {
850                resizeAndRepaint();
851            }
852        }
853    
854        /**
855         * Throws UnsupportedOperationException because variable height rows are
856         * not supported.
857         *
858         * @param row ignored
859         * @param rowHeight ignored
860         * @throws UnsupportedOperationException because variable height rows are
861         * not supported
862         */
863        @Override
864        public final void setRowHeight(int row, int rowHeight) {
865            throw new UnsupportedOperationException("variable height rows not supported");
866        }
867    
868        /**
869         * Sets the row height for this JXTreeTable and forwards the 
870         * row height to the renderering tree.
871         * 
872         * @param rowHeight height of a row.
873         */
874        @Override
875        public void setRowHeight(int rowHeight) {
876            super.setRowHeight(rowHeight);
877            adjustTreeRowHeight(getRowHeight()); 
878        }
879    
880        /**
881         * Forwards tableRowHeight to tree.
882         * 
883         * @param tableRowHeight height of a row.
884         */
885        protected void adjustTreeRowHeight(int tableRowHeight) {
886            if (renderer != null && renderer.getRowHeight() != tableRowHeight) {
887                renderer.setRowHeight(tableRowHeight);
888            }
889        }
890    
891        /**
892         * Forwards treeRowHeight to table. This is for completeness only: the
893         * rendering tree is under our total control, so we don't expect 
894         * any external call to tree.setRowHeight.
895         * 
896         * @param treeRowHeight height of a row.
897         */
898        protected void adjustTableRowHeight(int treeRowHeight) {
899            if (getRowHeight() != treeRowHeight) {
900                adminSetRowHeight(treeRowHeight);
901            }
902        }
903    
904    
905        /**
906         * <p>Overridden to ensure that private renderer state is kept in sync with the
907         * state of the component. Calls the inherited version after performing the
908         * necessary synchronization. If you override this method, make sure you call
909         * this version from your version of this method.</p>
910         *
911         * <p>This version maps the selection mode used by the renderer to match the
912         * selection mode specified for the table. Specifically, the modes are mapped
913         * as follows:
914         * <pre>
915         *  ListSelectionModel.SINGLE_INTERVAL_SELECTION: TreeSelectionModel.CONTIGUOUS_TREE_SELECTION;
916         *  ListSelectionModel.MULTIPLE_INTERVAL_SELECTION: TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION;
917         *  any other (default): TreeSelectionModel.SINGLE_TREE_SELECTION;
918         * </pre>
919         *
920         * {@inheritDoc}
921         *
922         * @param mode any of the table selection modes
923         */
924        @Override
925        public void setSelectionMode(int mode) {
926            if (renderer != null) {
927                switch (mode) {
928                    case ListSelectionModel.SINGLE_INTERVAL_SELECTION: {
929                        renderer.getSelectionModel().setSelectionMode(
930                            TreeSelectionModel.CONTIGUOUS_TREE_SELECTION);
931                        break;
932                    }
933                    case ListSelectionModel.MULTIPLE_INTERVAL_SELECTION: {
934                        renderer.getSelectionModel().setSelectionMode(
935                            TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
936                        break;
937                    }
938                    default: {
939                        renderer.getSelectionModel().setSelectionMode(
940                            TreeSelectionModel.SINGLE_TREE_SELECTION);
941                        break;
942                    }
943                }
944            }
945            super.setSelectionMode(mode);
946        }
947    
948        /**
949         * {@inheritDoc} <p>
950         * 
951         * Overridden to decorate the tree's renderer after calling super.
952         * At that point, it is only the tree itself that has been decorated. 
953         *
954         * @param renderer the <code>TableCellRenderer</code> to prepare
955         * @param row the row of the cell to render, where 0 is the first row
956         * @param column the column of the cell to render, where 0 is the first column
957         * @return the <code>Component</code> used as a stamp to render the specified cell
958         * 
959         * @see #applyRenderer(Component, ComponentAdapter)
960         */
961        @Override
962        public Component prepareRenderer(TableCellRenderer renderer, int row,
963            int column) {
964            Component component = super.prepareRenderer(renderer, row, column);
965            return applyRenderer(component, getComponentAdapter(row, column)); 
966        }
967    
968        /**
969         * Performs configuration of the tree's renderer if the adapter's column is
970         * the hierarchical column, does nothing otherwise.
971         * <p>
972         * 
973         * Note: this is legacy glue if the treeCellRenderer is of type
974         * DefaultTreeCellRenderer. In that case the renderer's
975         * background/foreground/Non/Selection colors are set to the tree's
976         * background/foreground depending on the adapter's selection state. Does
977         * nothing if the treeCellRenderer is backed by a ComponentProvider.
978         * 
979         * @param component the rendering component
980         * @param adapter component data adapter
981         * @throws NullPointerException if the specified component or adapter is
982         *         null
983         */
984        protected Component applyRenderer(Component component,
985                ComponentAdapter adapter) {
986            if (component == null) {
987                throw new IllegalArgumentException("null component");
988            }
989            if (adapter == null) {
990                throw new IllegalArgumentException("null component data adapter");
991            }
992    
993            if (isHierarchical(adapter.column)) {
994                // After all decorators have been applied, make sure that relevant
995                // attributes of the table cell renderer are applied to the
996                // tree cell renderer before the hierarchical column is rendered!
997                TreeCellRenderer tcr = renderer.getCellRenderer();
998                if (tcr instanceof JXTree.DelegatingRenderer) {
999                    tcr = ((JXTree.DelegatingRenderer) tcr).getDelegateRenderer();
1000    
1001                }
1002                if (tcr instanceof DefaultTreeCellRenderer) {
1003    
1004                    DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer) tcr);
1005                    // this effectively overwrites the dtcr settings
1006                    if (adapter.isSelected()) {
1007                        dtcr.setTextSelectionColor(component.getForeground());
1008                        dtcr.setBackgroundSelectionColor(component.getBackground());
1009                    } else {
1010                        dtcr.setTextNonSelectionColor(component.getForeground());
1011                        dtcr.setBackgroundNonSelectionColor(component
1012                                .getBackground());
1013                    }
1014                }
1015            }
1016            return component;
1017        }
1018    
1019        /**
1020         * Sets the specified TreeCellRenderer as the Tree cell renderer.
1021         *
1022         * @param cellRenderer to use for rendering tree cells.
1023         */
1024        public void setTreeCellRenderer(TreeCellRenderer cellRenderer) {
1025            if (renderer != null) {
1026                renderer.setCellRenderer(cellRenderer);
1027            }
1028        }
1029    
1030        public TreeCellRenderer getTreeCellRenderer() {
1031            return renderer.getCellRenderer();
1032        }
1033    
1034        
1035        @Override
1036        public String getToolTipText(MouseEvent event) {
1037            int column = columnAtPoint(event.getPoint());
1038            if (isHierarchical(column)) {
1039                int row = rowAtPoint(event.getPoint());
1040                return renderer.getToolTipText(event, row, column);
1041            }
1042            return super.getToolTipText(event);
1043        }
1044        
1045        /**
1046         * Sets the specified icon as the icon to use for rendering collapsed nodes.
1047         *
1048         * @param icon to use for rendering collapsed nodes
1049         * 
1050         * @see JXTree#setCollapsedIcon(Icon)
1051         */
1052        public void setCollapsedIcon(Icon icon) {
1053            renderer.setCollapsedIcon(icon);
1054        }
1055    
1056        /**
1057         * Sets the specified icon as the icon to use for rendering expanded nodes.
1058         *
1059         * @param icon to use for rendering expanded nodes
1060         * 
1061         * @see JXTree#setExpandedIcon(Icon)
1062         */
1063        public void setExpandedIcon(Icon icon) {
1064            renderer.setExpandedIcon(icon);
1065        }
1066    
1067        /**
1068         * Sets the specified icon as the icon to use for rendering open container nodes.
1069         *
1070         * @param icon to use for rendering open nodes
1071         * 
1072         * @see JXTree#setOpenIcon(Icon)
1073         */
1074        public void setOpenIcon(Icon icon) {
1075            renderer.setOpenIcon(icon);
1076        }
1077    
1078        /**
1079         * Sets the specified icon as the icon to use for rendering closed container nodes.
1080         *
1081         * @param icon to use for rendering closed nodes
1082         * 
1083         * @see JXTree#setClosedIcon(Icon)
1084         */
1085        public void setClosedIcon(Icon icon) {
1086            renderer.setClosedIcon(icon);
1087        }
1088    
1089        /**
1090         * Sets the specified icon as the icon to use for rendering leaf nodes.
1091         *
1092         * @param icon to use for rendering leaf nodes
1093         * 
1094         * @see JXTree#setLeafIcon(Icon)
1095         */
1096        public void setLeafIcon(Icon icon) {
1097            renderer.setLeafIcon(icon);
1098        }
1099    
1100        /**
1101         * Property to control whether per-tree icons should be 
1102         * copied to the renderer on setTreeCellRenderer. <p>
1103         * 
1104         * The default value is false.
1105         * 
1106         * @param overwrite a boolean to indicate if the per-tree Icons should
1107         *   be copied to the new renderer on setTreeCellRenderer.
1108         * 
1109         * @see #isOverwriteRendererIcons()  
1110         * @see #setLeafIcon(Icon)
1111         * @see #setOpenIcon(Icon)
1112         * @see #setClosedIcon(Icon) 
1113         * @see JXTree#setOverwriteRendererIcons(boolean) 
1114         */
1115        public void setOverwriteRendererIcons(boolean overwrite) {
1116            renderer.setOverwriteRendererIcons(overwrite);
1117        }
1118    
1119    
1120        /**
1121         * Returns a boolean indicating whether the per-tree icons should be 
1122         * copied to the renderer on setTreeCellRenderer.
1123         * 
1124         * @return true if a TreeCellRenderer's icons will be overwritten with the
1125         *   tree's Icons, false if the renderer's icons will be unchanged.
1126         *   
1127         * @see #setOverwriteRendererIcons(boolean)
1128         * @see #setLeafIcon(Icon)
1129         * @see #setOpenIcon(Icon)
1130         * @see #setClosedIcon(Icon)  
1131         * @see JXTree#isOverwriteRendererIcons()
1132         *     
1133         */
1134        public boolean isOverwriteRendererIcons() {
1135            return renderer.isOverwriteRendererIcons();
1136        }
1137        
1138        /**
1139         * Overridden to ensure that private renderer state is kept in sync with the
1140         * state of the component. Calls the inherited version after performing the
1141         * necessary synchronization. If you override this method, make sure you call
1142         * this version from your version of this method.
1143         */
1144        @Override
1145        public void clearSelection() {
1146            if (renderer != null) {
1147                renderer.clearSelection();
1148            }
1149            super.clearSelection();
1150        }
1151    
1152        /**
1153         * Collapses all nodes in the treetable.
1154         */
1155        public void collapseAll() {
1156            renderer.collapseAll();
1157        }
1158    
1159        /**
1160         * Expands all nodes in the treetable.
1161         */
1162        public void expandAll() {
1163            renderer.expandAll();
1164        }
1165    
1166        /**
1167         * Collapses the node at the specified path in the treetable.
1168         *
1169         * @param path path of the node to collapse
1170         */
1171        public void collapsePath(TreePath path) {
1172            renderer.collapsePath(path);
1173        }
1174    
1175        /**
1176         * Expands the the node at the specified path in the treetable.
1177         *
1178         * @param path path of the node to expand
1179         */
1180        public void expandPath(TreePath path) {
1181            renderer.expandPath(path);
1182        }
1183    
1184        /**
1185         * Makes sure all the path components in path are expanded (except
1186         * for the last path component) and scrolls so that the 
1187         * node identified by the path is displayed. Only works when this
1188         * <code>JTree</code> is contained in a <code>JScrollPane</code>.
1189         * 
1190         * (doc copied from JTree)
1191         * 
1192         * PENDING: JW - where exactly do we want to scroll? Here: the scroll
1193         * is in vertical direction only. Might need to show the tree column?
1194         * 
1195         * @param path  the <code>TreePath</code> identifying the node to
1196         *          bring into view
1197         */
1198        public void scrollPathToVisible(TreePath path) {
1199            renderer.scrollPathToVisible(path);
1200    //        if (path == null) return;
1201    //        renderer.makeVisible(path);
1202    //        int row = getRowForPath(path);
1203    //        scrollRowToVisible(row);
1204        }
1205    
1206        
1207        /**
1208         * Collapses the row in the treetable. If the specified row index is
1209         * not valid, this method will have no effect.
1210         */
1211        public void collapseRow(int row) {
1212            renderer.collapseRow(row);
1213        }
1214    
1215        /**
1216         * Expands the specified row in the treetable. If the specified row index is
1217         * not valid, this method will have no effect.
1218         */
1219        public void expandRow(int row) {
1220            renderer.expandRow(row);
1221        }
1222    
1223        
1224        /**
1225         * Returns true if the value identified by path is currently viewable, which
1226         * means it is either the root or all of its parents are expanded. Otherwise,
1227         * this method returns false.
1228         *
1229         * @return true, if the value identified by path is currently viewable;
1230         * false, otherwise
1231         */
1232        public boolean isVisible(TreePath path) {
1233            return renderer.isVisible(path);
1234        }
1235    
1236        /**
1237         * Returns true if the node identified by path is currently expanded.
1238         * Otherwise, this method returns false.
1239         *
1240         * @param path path
1241         * @return true, if the value identified by path is currently expanded;
1242         * false, otherwise
1243         */
1244        public boolean isExpanded(TreePath path) {
1245            return renderer.isExpanded(path);
1246        }
1247    
1248        /**
1249         * Returns true if the node at the specified display row is currently expanded.
1250         * Otherwise, this method returns false.
1251         *
1252         * @param row row
1253         * @return true, if the node at the specified display row is currently expanded.
1254         * false, otherwise
1255         */
1256        public boolean isExpanded(int row) {
1257            return renderer.isExpanded(row);
1258        }
1259    
1260        /**
1261         * Returns true if the node identified by path is currently collapsed, 
1262         * this will return false if any of the values in path are currently not 
1263         * being displayed.   
1264         *
1265         * @param path path
1266         * @return true, if the value identified by path is currently collapsed;
1267         * false, otherwise
1268         */
1269        public boolean isCollapsed(TreePath path) {
1270            return renderer.isCollapsed(path);
1271        }
1272    
1273        /**
1274         * Returns true if the node at the specified display row is collapsed.
1275         *
1276         * @param row row
1277         * @return true, if the node at the specified display row is currently collapsed.
1278         * false, otherwise
1279         */
1280        public boolean isCollapsed(int row) {
1281            return renderer.isCollapsed(row);
1282        }
1283    
1284        
1285        /**
1286         * Returns an <code>Enumeration</code> of the descendants of the
1287         * path <code>parent</code> that
1288         * are currently expanded. If <code>parent</code> is not currently
1289         * expanded, this will return <code>null</code>.
1290         * If you expand/collapse nodes while
1291         * iterating over the returned <code>Enumeration</code>
1292         * this may not return all
1293         * the expanded paths, or may return paths that are no longer expanded.
1294         *
1295         * @param parent  the path which is to be examined
1296         * @return an <code>Enumeration</code> of the descendents of 
1297         *        <code>parent</code>, or <code>null</code> if
1298         *        <code>parent</code> is not currently expanded
1299         */
1300        
1301        public Enumeration<?> getExpandedDescendants(TreePath parent) {
1302            return renderer.getExpandedDescendants(parent);
1303        }
1304    
1305        
1306        /**
1307         * Returns the TreePath for a given x,y location.
1308         *
1309         * @param x x value
1310         * @param y y value
1311         *
1312         * @return the <code>TreePath</code> for the givern location.
1313         */
1314         public TreePath getPathForLocation(int x, int y) {
1315            int row = rowAtPoint(new Point(x,y));
1316            if (row == -1) {
1317              return null;  
1318            }
1319            return renderer.getPathForRow(row);
1320         }
1321    
1322        /**
1323         * Returns the TreePath for a given row.
1324         *
1325         * @param row
1326         *
1327         * @return the <code>TreePath</code> for the given row.
1328         */
1329         public TreePath getPathForRow(int row) {
1330            return renderer.getPathForRow(row);
1331         }
1332    
1333         /**
1334          * Returns the row for a given TreePath.
1335          *
1336          * @param path
1337          * @return the row for the given <code>TreePath</code>.
1338          */
1339         public int getRowForPath(TreePath path) {
1340           return renderer.getRowForPath(path);
1341         }
1342    
1343    //------------------------------ exposed Tree properties
1344    
1345         /**
1346          * Determines whether or not the root node from the TreeModel is visible.
1347          *
1348          * @param visible true, if the root node is visible; false, otherwise
1349          */
1350         public void setRootVisible(boolean visible) {
1351             renderer.setRootVisible(visible);
1352             // JW: the revalidate forces the root to appear after a 
1353             // toggling a visible from an initially invisible root.
1354             // JTree fires a propertyChange on the ROOT_VISIBLE_PROPERTY
1355             // BasicTreeUI reacts by (ultimately) calling JTree.treeDidChange
1356             // which revalidate the tree part. 
1357             // Might consider to listen for the propertyChange (fired only if there
1358             // actually was a change) instead of revalidating unconditionally.
1359             revalidate();
1360             repaint();
1361         }
1362    
1363         /**
1364          * Returns true if the root node of the tree is displayed.
1365          *
1366          * @return true if the root node of the tree is displayed
1367          */
1368         public boolean isRootVisible() {
1369             return renderer.isRootVisible();
1370         }
1371    
1372    
1373        /**
1374         * Sets the value of the <code>scrollsOnExpand</code> property for the tree
1375         * part. This property specifies whether the expanded paths should be scrolled
1376         * into view. In a look and feel in which a tree might not need to scroll
1377         * when expanded, this property may be ignored.
1378         *
1379         * @param scroll true, if expanded paths should be scrolled into view;
1380         * false, otherwise
1381         */
1382        public void setScrollsOnExpand(boolean scroll) {
1383            renderer.setScrollsOnExpand(scroll);
1384        }
1385    
1386        /**
1387         * Returns the value of the <code>scrollsOnExpand</code> property.
1388         *
1389         * @return the value of the <code>scrollsOnExpand</code> property
1390         */
1391        public boolean getScrollsOnExpand() {
1392            return renderer.getScrollsOnExpand();
1393        }
1394    
1395        /**
1396         * Sets the value of the <code>showsRootHandles</code> property for the tree
1397         * part. This property specifies whether the node handles should be displayed.
1398         * If handles are not supported by a particular look and feel, this property
1399         * may be ignored.
1400         *
1401         * @param visible true, if root handles should be shown; false, otherwise
1402         */
1403        public void setShowsRootHandles(boolean visible) {
1404            renderer.setShowsRootHandles(visible);
1405            repaint();
1406        }
1407    
1408        /**
1409         * Returns the value of the <code>showsRootHandles</code> property.
1410         *
1411         * @return the value of the <code>showsRootHandles</code> property
1412         */
1413        public boolean getShowsRootHandles() {
1414            return renderer.getShowsRootHandles();
1415        }
1416    
1417        /**
1418         * Sets the value of the <code>expandsSelectedPaths</code> property for the tree
1419         * part. This property specifies whether the selected paths should be expanded.
1420         *
1421         * @param expand true, if selected paths should be expanded; false, otherwise
1422         */
1423        public void setExpandsSelectedPaths(boolean expand) {
1424            renderer.setExpandsSelectedPaths(expand);
1425        }
1426    
1427        /**
1428         * Returns the value of the <code>expandsSelectedPaths</code> property.
1429         *
1430         * @return the value of the <code>expandsSelectedPaths</code> property
1431         */
1432        public boolean getExpandsSelectedPaths() {
1433            return renderer.getExpandsSelectedPaths();
1434        }
1435    
1436    
1437        /**
1438         * Returns the number of mouse clicks needed to expand or close a node.
1439         *
1440         * @return number of mouse clicks before node is expanded
1441         */
1442        public int getToggleClickCount() {
1443            return renderer.getToggleClickCount();
1444        }
1445    
1446        /**
1447         * Sets the number of mouse clicks before a node will expand or close.
1448         * The default is two. 
1449         *
1450         * @param clickCount the number of clicks required to expand/collapse a node.
1451         */
1452        public void setToggleClickCount(int clickCount) {
1453            renderer.setToggleClickCount(clickCount);
1454        }
1455    
1456        /**
1457         * Returns true if the tree is configured for a large model.
1458         * The default value is false.
1459         * 
1460         * @return true if a large model is suggested
1461         * @see #setLargeModel
1462         */
1463        public boolean isLargeModel() {
1464            return renderer.isLargeModel();
1465        }
1466    
1467        /**
1468         * Specifies whether the UI should use a large model.
1469         * (Not all UIs will implement this.) <p>
1470         * 
1471         * <strong>NOTE</strong>: this method is exposed for completeness - 
1472         * currently it's not recommended 
1473         * to use a large model because there are some issues 
1474         * (not yet fully understood), namely
1475         * issue #25-swingx, and probably #270-swingx. 
1476         * 
1477         * @param newValue true to suggest a large model to the UI
1478         */
1479        public void setLargeModel(boolean newValue) {
1480            renderer.setLargeModel(newValue);
1481            // JW: random method calling ... doesn't help
1482    //        renderer.treeDidChange();
1483    //        revalidate();
1484    //        repaint();
1485        }
1486    
1487    //------------------------------ exposed tree listeners
1488        
1489        /**
1490         * Adds a listener for <code>TreeExpansion</code> events.
1491         * 
1492         * TODO (JW): redirect event source to this. 
1493         * 
1494         * @param tel a TreeExpansionListener that will be notified 
1495         * when a tree node is expanded or collapsed
1496         */
1497        public void addTreeExpansionListener(TreeExpansionListener tel) {
1498            renderer.addTreeExpansionListener(tel);
1499        }
1500    
1501        /**
1502         * Removes a listener for <code>TreeExpansion</code> events.
1503         * @param tel the <code>TreeExpansionListener</code> to remove
1504         */
1505        public void removeTreeExpansionListener(TreeExpansionListener tel) {
1506            renderer.removeTreeExpansionListener(tel);
1507        }
1508    
1509        /**
1510         * Adds a listener for <code>TreeSelection</code> events.
1511         * TODO (JW): redirect event source to this. 
1512         * 
1513         * @param tsl a TreeSelectionListener that will be notified 
1514         * when a tree node is selected or deselected
1515         */
1516        public void addTreeSelectionListener(TreeSelectionListener tsl) {
1517            renderer.addTreeSelectionListener(tsl);
1518        }
1519    
1520        /**
1521         * Removes a listener for <code>TreeSelection</code> events.
1522         * @param tsl the <code>TreeSelectionListener</code> to remove
1523         */
1524        public void removeTreeSelectionListener(TreeSelectionListener tsl) {
1525            renderer.removeTreeSelectionListener(tsl);
1526        }
1527    
1528        /**
1529         * Adds a listener for <code>TreeWillExpand</code> events.
1530         * TODO (JW): redirect event source to this. 
1531         * 
1532         * @param tel a TreeWillExpandListener that will be notified 
1533         * when a tree node will be expanded or collapsed 
1534         */
1535        public void addTreeWillExpandListener(TreeWillExpandListener tel) {
1536            renderer.addTreeWillExpandListener(tel);
1537        }
1538    
1539        /**
1540         * Removes a listener for <code>TreeWillExpand</code> events.
1541         * @param tel the <code>TreeWillExpandListener</code> to remove
1542         */
1543        public void removeTreeWillExpandListener(TreeWillExpandListener tel) {
1544            renderer.removeTreeWillExpandListener(tel);
1545         }
1546     
1547        
1548        /**
1549         * Returns the selection model for the tree portion of the this treetable.
1550         *
1551         * @return selection model for the tree portion of the this treetable
1552         */
1553        public TreeSelectionModel getTreeSelectionModel() {
1554            return renderer.getSelectionModel();    // RG: Fix JDNC issue 41
1555        }
1556    
1557        /**
1558         * Overriden to invoke supers implementation, and then,
1559         * if the receiver is editing a Tree column, the editors bounds is
1560         * reset. The reason we have to do this is because JTable doesn't
1561         * think the table is being edited, as <code>getEditingRow</code> returns
1562         * -1, and therefore doesn't automaticly resize the editor for us.
1563         */
1564        @Override
1565        public void sizeColumnsToFit(int resizingColumn) {
1566            /** TODO: Review wrt doLayout() */
1567            super.sizeColumnsToFit(resizingColumn);
1568            // rg:changed
1569            if (getEditingColumn() != -1 && isHierarchical(editingColumn)) {
1570                Rectangle cellRect = getCellRect(realEditingRow(),
1571                    getEditingColumn(), false);
1572                Component component = getEditorComponent();
1573                component.setBounds(cellRect);
1574                component.validate();
1575            }
1576        }
1577    
1578    
1579        /**
1580         * Determines if the specified column is defined as the hierarchical column.
1581         * 
1582         * @param column
1583         *            zero-based index of the column in view coordinates
1584         * @return true if the column is the hierarchical column; false otherwise.
1585         * @throws IllegalArgumentException
1586         *             if the column is less than 0 or greater than or equal to the
1587         *             column count
1588         */
1589        public boolean isHierarchical(int column) {
1590            if (column < 0 || column >= getColumnCount()) {
1591                throw new IllegalArgumentException("column must be valid, was" + column);
1592            }
1593            
1594            return (getHierarchicalColumn() == column);
1595        }
1596    
1597        /**
1598         * Returns the index of the hierarchical column. This is the column that is
1599         * displayed as the tree.
1600         * 
1601         * @return the index of the hierarchical column, -1 if there is
1602         *   no hierarchical column
1603         * 
1604         */
1605        public int getHierarchicalColumn() {
1606            return convertColumnIndexToView(((TreeTableModel) renderer.getModel()).getHierarchicalColumn());
1607        }
1608        
1609        /**
1610         * {@inheritDoc}
1611         */
1612        @Override
1613        public TableCellRenderer getCellRenderer(int row, int column) {
1614            if (isHierarchical(column)) {
1615                return renderer;
1616            }
1617            
1618            return super.getCellRenderer(row, column);
1619        }
1620    
1621        /**
1622         * {@inheritDoc}
1623         */
1624        @Override
1625        public TableCellEditor getCellEditor(int row, int column) {
1626            if (isHierarchical(column)) {
1627                return hierarchicalEditor;
1628            }
1629            
1630            return super.getCellEditor(row, column);
1631        }
1632        
1633        
1634        @Override
1635        public void updateUI() {
1636            super.updateUI();
1637            updateHierarchicalRendererEditor();
1638        }
1639    
1640        /**
1641         * Updates Ui of renderer/editor for the hierarchical column. Need to do so
1642         * manually, as not accessible by the default lookup.
1643         */
1644        protected void updateHierarchicalRendererEditor() {
1645            if (renderer != null) {
1646               SwingUtilities.updateComponentTreeUI(renderer);
1647            }
1648        }
1649    
1650        /**
1651         * {@inheritDoc} <p>
1652         * 
1653         * Overridden to message the tree directly if the column is the view index of
1654         * the hierarchical column. <p>
1655         * 
1656         * PENDING JW: revisit once we switch to really using a table renderer. As is, it's
1657         * a quick fix for #821-swingx: string rep for hierarchical column incorrect.
1658         */
1659        @Override
1660        public String getStringAt(int row, int column) {
1661            if (isHierarchical(column)) {
1662                return getHierarchicalStringAt(row);
1663            }
1664            return super.getStringAt(row, column);
1665        }
1666    
1667        /**
1668         * Returns the String representation of the hierarchical column at the given 
1669         * row. <p>
1670         * 
1671         * @param row the row index in view coordinates
1672         * @return the string representation of the hierarchical column at the given row.
1673         * 
1674         * @see #getStringAt(int, int)
1675         */
1676        private String getHierarchicalStringAt(int row) {
1677            return renderer.getStringAt(row);
1678        }
1679    
1680        /**
1681         * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
1682         * to listen for changes in the ListSelectionModel it maintains. Once
1683         * a change in the ListSelectionModel happens, the paths are updated
1684         * in the DefaultTreeSelectionModel.
1685         */
1686        class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel {
1687            /** Set to true when we are updating the ListSelectionModel. */
1688            protected boolean updatingListSelectionModel;
1689    
1690            public ListToTreeSelectionModelWrapper() {
1691                super();
1692                getListSelectionModel().addListSelectionListener
1693                    (createListSelectionListener());
1694            }
1695    
1696            /**
1697             * Returns the list selection model. ListToTreeSelectionModelWrapper
1698             * listens for changes to this model and updates the selected paths
1699             * accordingly.
1700             */
1701            ListSelectionModel getListSelectionModel() {
1702                return listSelectionModel;
1703            }
1704    
1705            /**
1706             * This is overridden to set <code>updatingListSelectionModel</code>
1707             * and message super. This is the only place DefaultTreeSelectionModel
1708             * alters the ListSelectionModel.
1709             */
1710            @Override
1711            public void resetRowSelection() {
1712                if (!updatingListSelectionModel) {
1713                    updatingListSelectionModel = true;
1714                    try {
1715                        super.resetRowSelection();
1716                    }
1717                    finally {
1718                        updatingListSelectionModel = false;
1719                    }
1720                }
1721                // Notice how we don't message super if
1722                // updatingListSelectionModel is true. If
1723                // updatingListSelectionModel is true, it implies the
1724                // ListSelectionModel has already been updated and the
1725                // paths are the only thing that needs to be updated.
1726            }
1727    
1728            /**
1729             * Creates and returns an instance of ListSelectionHandler.
1730             */
1731            protected ListSelectionListener createListSelectionListener() {
1732                return new ListSelectionHandler();
1733            }
1734    
1735            /**
1736             * If <code>updatingListSelectionModel</code> is false, this will
1737             * reset the selected paths from the selected rows in the list
1738             * selection model.
1739             */
1740            protected void updateSelectedPathsFromSelectedRows() {
1741                if (!updatingListSelectionModel) {
1742                    updatingListSelectionModel = true;
1743                    try {
1744                        if (listSelectionModel.isSelectionEmpty()) {
1745                            clearSelection();
1746                        } else {
1747                            // This is way expensive, ListSelectionModel needs an
1748                            // enumerator for iterating.
1749                            int min = listSelectionModel.getMinSelectionIndex();
1750                            int max = listSelectionModel.getMaxSelectionIndex();
1751    
1752                            List<TreePath> paths = new ArrayList<TreePath>();
1753                            for (int counter = min; counter <= max; counter++) {
1754                                if (listSelectionModel.isSelectedIndex(counter)) {
1755                                    TreePath selPath = renderer.getPathForRow(
1756                                        counter);
1757    
1758                                    if (selPath != null) {
1759                                        paths.add(selPath);
1760                                    }
1761                                }
1762                            }
1763                            setSelectionPaths(paths.toArray(new TreePath[paths.size()]));
1764                            // need to force here: usually the leadRow is adjusted 
1765                            // in resetRowSelection which is disabled during this method
1766                            leadRow = leadIndex;
1767                        }
1768                    }
1769                    finally {
1770                        updatingListSelectionModel = false;
1771                    }
1772                }
1773            }
1774    
1775            /**
1776             * Class responsible for calling updateSelectedPathsFromSelectedRows
1777             * when the selection of the list changse.
1778             */
1779            class ListSelectionHandler implements ListSelectionListener {
1780                public void valueChanged(ListSelectionEvent e) {
1781                    if (!e.getValueIsAdjusting()) {
1782                        updateSelectedPathsFromSelectedRows();
1783                    }
1784                }
1785            }
1786        }
1787    
1788        /**
1789         * 
1790         */
1791        protected static class TreeTableModelAdapter extends AbstractTableModel {
1792            private TreeModelListener treeModelListener;
1793            
1794            /**
1795             * Maintains a TreeTableModel and a JTree as purely implementation details.
1796             * Developers can plug in any type of custom TreeTableModel through a
1797             * JXTreeTable constructor or through setTreeTableModel().
1798             *
1799             * @param model Underlying data model for the JXTreeTable that will ultimately
1800             * be bound to this TreeTableModelAdapter
1801             * @param tree TreeTableCellRenderer instantiated with the same model as
1802             * specified by the model parameter of this constructor
1803             * @throws IllegalArgumentException if a null model argument is passed
1804             * @throws IllegalArgumentException if a null tree argument is passed
1805             */
1806            TreeTableModelAdapter(JTree tree) {
1807                assert tree != null;
1808    
1809                this.tree = tree; // need tree to implement getRowCount()
1810                tree.getModel().addTreeModelListener(getTreeModelListener());
1811                tree.addTreeExpansionListener(new TreeExpansionListener() {
1812                    // Don't use fireTableRowsInserted() here; the selection model
1813                    // would get updated twice.
1814                    public void treeExpanded(TreeExpansionEvent event) {
1815                        updateAfterExpansionEvent(event);
1816                    }
1817    
1818                    public void treeCollapsed(TreeExpansionEvent event) {
1819                        updateAfterExpansionEvent(event);
1820                    }
1821                });
1822                tree.addPropertyChangeListener("model", new PropertyChangeListener() {
1823                    public void propertyChange(PropertyChangeEvent evt) {
1824                        TreeTableModel model = (TreeTableModel) evt.getOldValue();
1825                        model.removeTreeModelListener(getTreeModelListener());
1826                        
1827                        model = (TreeTableModel) evt.getNewValue();
1828                        model.addTreeModelListener(getTreeModelListener());
1829                        
1830                        fireTableStructureChanged();
1831                    }
1832                });
1833            }
1834    
1835            /**
1836             * updates the table after having received an TreeExpansionEvent.<p>
1837             * 
1838             * @param event the TreeExpansionEvent which triggered the method call.
1839             */
1840            protected void updateAfterExpansionEvent(TreeExpansionEvent event) {
1841                // moved to let the renderer handle directly
1842    //            treeTable.getTreeTableHacker().setExpansionChangedFlag();
1843                // JW: delayed fire leads to a certain sluggishness occasionally? 
1844                fireTableDataChanged();
1845            }
1846    
1847            /**
1848             * Returns the JXTreeTable instance to which this TreeTableModelAdapter is
1849             * permanently and exclusively bound. For use by
1850             * {@link org.jdesktop.swingx.JXTreeTable#setModel(javax.swing.table.TableModel)}.
1851             *
1852             * @return JXTreeTable to which this TreeTableModelAdapter is permanently bound
1853             */
1854            protected JXTreeTable getTreeTable() {
1855                return treeTable;
1856            }
1857    
1858            /**
1859             * Immutably binds this TreeTableModelAdapter to the specified JXTreeTable.
1860             *
1861             * @param treeTable the JXTreeTable instance that this adapter is bound to.
1862             */
1863            protected final void bind(JXTreeTable treeTable) {
1864                // Suppress potentially subversive invocation!
1865                // Prevent clearing out the deck for possible hijack attempt later!
1866                if (treeTable == null) {
1867                    throw new IllegalArgumentException("null treeTable");
1868                }
1869    
1870                if (this.treeTable == null) {
1871                    this.treeTable = treeTable;
1872                }
1873                else {
1874                    throw new IllegalArgumentException("adapter already bound");
1875                }
1876            }
1877    
1878            // Wrappers, implementing TableModel interface.
1879            // TableModelListener management provided by AbstractTableModel superclass.
1880    
1881            @Override
1882            public Class<?> getColumnClass(int column) {
1883                return ((TreeTableModel) tree.getModel()).getColumnClass(column);
1884            }
1885    
1886            public int getColumnCount() {
1887                return ((TreeTableModel) tree.getModel()).getColumnCount();
1888            }
1889    
1890            @Override
1891            public String getColumnName(int column) {
1892                return ((TreeTableModel) tree.getModel()).getColumnName(column);
1893            }
1894    
1895            public int getRowCount() {
1896                return tree.getRowCount();
1897            }
1898    
1899            public Object getValueAt(int row, int column) {
1900                // Issue #270-swingx: guard against invisible row
1901                Object node = nodeForRow(row);
1902                return node != null ? ((TreeTableModel) tree.getModel()).getValueAt(node, column) : null;
1903            }
1904    
1905            @Override
1906            public boolean isCellEditable(int row, int column) {
1907                // Issue #270-swingx: guard against invisible row
1908                Object node = nodeForRow(row);
1909                return node != null ? ((TreeTableModel) tree.getModel()).isCellEditable(node, column) : false;
1910            }
1911    
1912            @Override
1913            public void setValueAt(Object value, int row, int column) {
1914                // Issue #270-swingx: guard against invisible row
1915                Object node = nodeForRow(row);
1916                if (node != null) {
1917                    ((TreeTableModel) tree.getModel()).setValueAt(value, node, column);
1918                }
1919            }
1920    
1921            protected Object nodeForRow(int row) {
1922                // Issue #270-swingx: guard against invisible row
1923                TreePath path = tree.getPathForRow(row);
1924                return path != null ? path.getLastPathComponent() : null;
1925            }
1926    
1927            /**
1928             * @return <code>TreeModelListener</code>
1929             */
1930            private TreeModelListener getTreeModelListener() {
1931                if (treeModelListener == null) {
1932                    treeModelListener = new TreeModelListener() {
1933                        
1934                        public void treeNodesChanged(TreeModelEvent e) {
1935    //                        LOG.info("got tree event: changed " + e);
1936                            delayedFireTableDataUpdated(e);
1937                        }   
1938    
1939                        // We use delayedFireTableDataChanged as we can
1940                        // not be guaranteed the tree will have finished processing
1941                        // the event before us.
1942                        public void treeNodesInserted(TreeModelEvent e) {
1943                            delayedFireTableDataChanged(e, 1);
1944                        }
1945    
1946                        public void treeNodesRemoved(TreeModelEvent e) {
1947    //                        LOG.info("got tree event: removed " + e);
1948                           delayedFireTableDataChanged(e, 2);
1949                        }
1950    
1951                        public void treeStructureChanged(TreeModelEvent e) {
1952                            // ?? should be mapped to structureChanged -- JW
1953                            if (isTableStructureChanged(e)) {
1954                                delayedFireTableStructureChanged();
1955                            } else {
1956                                delayedFireTableDataChanged();
1957                            }
1958                        }
1959                    };
1960                }
1961                
1962                return treeModelListener;
1963            }
1964    
1965            /**
1966             * Decides if the given treeModel structureChanged should 
1967             * trigger a table structureChanged. Returns true if the 
1968             * source path is the root or null, false otherwise.<p>
1969             * 
1970             * PENDING: need to refine? "Marker" in Event-Object?
1971             * 
1972             * @param e the TreeModelEvent received in the treeModelListener's 
1973             *   treeStructureChanged
1974             * @return a boolean indicating whether the given TreeModelEvent
1975             *   should trigger a structureChanged.
1976             */
1977            private boolean isTableStructureChanged(TreeModelEvent e) {
1978                if ((e.getTreePath() == null) ||
1979                        (e.getTreePath().getParentPath() == null)) return true;
1980                return false;
1981            }
1982    
1983            /**
1984             * Invokes fireTableDataChanged after all the pending events have been
1985             * processed. SwingUtilities.invokeLater is used to handle this.
1986             */
1987            private void delayedFireTableStructureChanged() {
1988                SwingUtilities.invokeLater(new Runnable() {
1989                    public void run() {
1990                        fireTableStructureChanged();
1991                    }
1992                });
1993            }
1994    
1995            /**
1996             * Invokes fireTableDataChanged after all the pending events have been
1997             * processed. SwingUtilities.invokeLater is used to handle this.
1998             */
1999            private void delayedFireTableDataChanged() {
2000                SwingUtilities.invokeLater(new Runnable() {
2001                    public void run() {
2002                        fireTableDataChanged();
2003                    }
2004                });
2005            }
2006    
2007            /**
2008             * Invokes fireTableDataChanged after all the pending events have been
2009             * processed. SwingUtilities.invokeLater is used to handle this.
2010             * Allowed event types: 1 for insert, 2 for delete
2011             */
2012            private void delayedFireTableDataChanged(final TreeModelEvent tme, final int typeChange) {
2013                if ((typeChange < 1 ) || (typeChange > 2)) 
2014                    throw new IllegalArgumentException("Event type must be 1 or 2, was " + typeChange);
2015                // expansion state before invoke may be different 
2016                // from expansion state in invoke 
2017                final boolean expanded = tree.isExpanded(tme.getTreePath());
2018                // quick test if tree throws for unrelated path. Seems like not.
2019    //            tree.getRowForPath(new TreePath("dummy"));
2020                SwingUtilities.invokeLater(new Runnable() {
2021                    public void run() {
2022                        int indices[] = tme.getChildIndices();
2023                        TreePath path = tme.getTreePath();
2024                        // quick test to see if bailing out is an option
2025    //                    if (false) {
2026                        if (indices != null) {
2027                            if (expanded) { // Dont bother to update if the parent
2028                                // node is collapsed
2029                                // indices must in ascending order, as per TreeEvent/Listener doc
2030                                int min = indices[0];
2031                                int max = indices[indices.length - 1];
2032                                int startingRow = tree.getRowForPath(path) + 1;
2033                                min = startingRow + min;
2034                                max = startingRow + max;
2035                                switch (typeChange) {
2036                                case 1:
2037    //                                LOG.info("rows inserted: path " + path + "/" + min + "/"
2038    //                                        + max);
2039                                    fireTableRowsInserted(min, max);
2040                                    break;
2041                                case 2:
2042    //                                LOG.info("rows deleted path " + path + "/" + min + "/"
2043    //                                                + max);
2044                                    fireTableRowsDeleted(min, max);
2045                                    break;
2046                                }
2047                            } else {
2048                                // not expanded - but change might effect appearance
2049                                // of parent
2050                                // Issue #82-swingx
2051                                int row = tree.getRowForPath(path);
2052                                // fix Issue #247-swingx: prevent accidental
2053                                // structureChanged
2054                                // for collapsed path
2055                                // in this case row == -1, which ==
2056                                // TableEvent.HEADER_ROW
2057                                if (row >= 0)
2058                                    fireTableRowsUpdated(row, row);
2059                            }
2060                        } else { // case where the event is fired to identify
2061                                    // root.
2062                            fireTableDataChanged();
2063                        }
2064                    }
2065                });
2066            }
2067    
2068            /**
2069             * This is used for updated only. PENDING: not necessary to delay?
2070             * Updates are never structural changes which are the critical.
2071             * 
2072             * @param tme
2073             */
2074            protected void delayedFireTableDataUpdated(final TreeModelEvent tme) {
2075                final boolean expanded = tree.isExpanded(tme.getTreePath());
2076                SwingUtilities.invokeLater(new Runnable() {
2077                    public void run() {
2078                        int indices[] = tme.getChildIndices();
2079                        TreePath path = tme.getTreePath();
2080                        if (indices != null) {
2081                            if (expanded) { // Dont bother to update if the parent
2082                                // node is collapsed
2083                                Object children[] = tme.getChildren();
2084                                // can we be sure that children.length > 0?
2085                                // int min = tree.getRowForPath(path.pathByAddingChild(children[0]));
2086                                // int max = tree.getRowForPath(path.pathByAddingChild(children[children.length -1]));
2087                                int min = Integer.MAX_VALUE;
2088                                int max = Integer.MIN_VALUE;
2089                                for (int i = 0; i < indices.length; i++) {
2090                                    Object child = children[i];
2091                                    TreePath childPath = path
2092                                            .pathByAddingChild(child);
2093                                    int index = tree.getRowForPath(childPath);
2094                                    if (index < min) {
2095                                        min = index;
2096                                    }
2097                                    if (index > max) {
2098                                        max = index;
2099                                    }
2100                                }
2101    //                            LOG.info("Updated: parentPath/min/max" + path + "/" + min + "/" + max);
2102                                // JW: the index is occasionally - 1 - need further digging 
2103                                fireTableRowsUpdated(Math.max(0, min), Math.max(0, max));
2104                            } else {
2105                                // not expanded - but change might effect appearance
2106                                // of parent Issue #82-swingx
2107                                int row = tree.getRowForPath(path);
2108                                // fix Issue #247-swingx: prevent accidental structureChanged
2109                                // for collapsed path in this case row == -1, 
2110                                // which == TableEvent.HEADER_ROW
2111                                if (row >= 0)
2112                                    fireTableRowsUpdated(row, row);
2113                            }
2114                        } else { // case where the event is fired to identify
2115                                    // root.
2116                            fireTableDataChanged();
2117                        }
2118                    }
2119                });
2120    
2121            }
2122    
2123            private final JTree tree; // immutable
2124            private JXTreeTable treeTable = null; // logically immutable
2125        }
2126    
2127        static class TreeTableCellRenderer extends JXTree implements
2128            TableCellRenderer
2129            // need to implement RolloverRenderer
2130            // PENDING JW: method name clash rolloverRenderer.isEnabled and
2131            // component.isEnabled .. don't extend, use? And change
2132            // the method name in rolloverRenderer? 
2133            // commented - so doesn't show the rollover cursor.
2134            // 
2135    //      ,  RolloverRenderer 
2136            {
2137            private PropertyChangeListener rolloverListener;
2138    
2139            // Force user to specify TreeTableModel instead of more general
2140            // TreeModel
2141            public TreeTableCellRenderer(TreeTableModel model) {
2142                super(model);
2143                putClientProperty("JTree.lineStyle", "None");
2144                setRootVisible(false); // superclass default is "true"
2145                setShowsRootHandles(true); // superclass default is "false"
2146                    /**
2147                     * TODO: Support truncated text directly in
2148                     * DefaultTreeCellRenderer.
2149                     */
2150                // removed as fix for #769-swingx: defaults for treetable should be same as tree
2151    //            setOverwriteRendererIcons(true);
2152    // setCellRenderer(new DefaultTreeRenderer());
2153                setCellRenderer(new ClippedTreeCellRenderer());
2154            }
2155    
2156            
2157            /**
2158             * {@inheritDoc} <p>
2159             * 
2160             * Overridden to hack around #766-swingx: cursor flickering in DnD
2161             * when dragging over tree column. This is a core bug (#6700748) related
2162             * to painting the rendering component on a CellRendererPane. A trick
2163             * around is to let this return false. <p>
2164             * 
2165             * This implementation applies the trick, that is returns false always. 
2166             * The hack can be disabled by setting the treeTable's client property
2167             * DROP_HACK_FLAG_KEY to Boolean.FALSE. 
2168             * 
2169             */
2170            @Override
2171            public boolean isVisible() {
2172                return shouldApplyDropHack() ? false : super.isVisible();
2173            }
2174    
2175    
2176            /**
2177             * Returns a boolean indicating whether the drop hack should be applied.
2178             * 
2179             * @return a boolean indicating whether the drop hack should be applied.
2180             */
2181            protected boolean shouldApplyDropHack() {
2182                return !Boolean.FALSE.equals(treeTable.getClientProperty(DROP_HACK_FLAG_KEY));
2183            }
2184    
2185    
2186            /**
2187             * Hack around #297-swingx: tooltips shown at wrong row.
2188             * 
2189             * The problem is that - due to much tricksery when rendering the tree -
2190             * the given coordinates are rather useless. As a consequence, super
2191             * maps to wrong coordinates. This takes over completely.
2192             * 
2193             * PENDING: bidi?
2194             * 
2195             * @param event the mouseEvent in treetable coordinates
2196             * @param row the view row index
2197             * @param column the view column index
2198             * @return the tooltip as appropriate for the given row
2199             */
2200            private String getToolTipText(MouseEvent event, int row, int column) {
2201                if (row < 0) return null;
2202                String toolTip = null;
2203                TreeCellRenderer renderer = getCellRenderer();
2204                TreePath     path = getPathForRow(row);
2205                Object       lastPath = path.getLastPathComponent();
2206                Component    rComponent = renderer.getTreeCellRendererComponent
2207                    (this, lastPath, isRowSelected(row),
2208                     isExpanded(row), getModel().isLeaf(lastPath), row,
2209                     true);
2210    
2211                if(rComponent instanceof JComponent) {
2212                    Rectangle       pathBounds = getPathBounds(path);
2213                    Rectangle cellRect = treeTable.getCellRect(row, column, false);
2214                    // JW: what we are after
2215                    // is the offset into the hierarchical column 
2216                    // then intersect this with the pathbounds   
2217                    Point mousePoint = event.getPoint();
2218                    // translate to coordinates relative to cell
2219                    mousePoint.translate(-cellRect.x, -cellRect.y);
2220                    // translate horizontally to 
2221                    mousePoint.translate(-pathBounds.x, 0);
2222                    // show tooltip only if over renderer?
2223    //                if (mousePoint.x < 0) return null;
2224    //                p.translate(-pathBounds.x, -pathBounds.y);
2225                    MouseEvent newEvent = new MouseEvent(rComponent, event.getID(),
2226                          event.getWhen(),
2227                          event.getModifiers(),
2228                          mousePoint.x, 
2229                          mousePoint.y,
2230    //                    p.x, p.y, 
2231                          event.getClickCount(),
2232                          event.isPopupTrigger());
2233                    
2234                    toolTip = ((JComponent)rComponent).getToolTipText(newEvent);
2235                }
2236                if (toolTip != null) {
2237                    return toolTip;
2238                }
2239                return getToolTipText();
2240            }
2241    
2242            /**
2243             * {@inheritDoc} <p>
2244             * 
2245             * Overridden to not automatically de/register itself from/to the ToolTipManager.
2246             * As rendering component it is not considered to be active in any way, so the
2247             * manager must not listen. 
2248             */
2249            @Override
2250            public void setToolTipText(String text) {
2251                putClientProperty(TOOL_TIP_TEXT_KEY, text);
2252            }
2253    
2254            /**
2255             * Immutably binds this TreeTableModelAdapter to the specified JXTreeTable.
2256             * For internal use by JXTreeTable only.
2257             *
2258             * @param treeTable the JXTreeTable instance that this renderer is bound to
2259             */
2260            public final void bind(JXTreeTable treeTable) {
2261                // Suppress potentially subversive invocation!
2262                // Prevent clearing out the deck for possible hijack attempt later!
2263                if (treeTable == null) {
2264                    throw new IllegalArgumentException("null treeTable");
2265                }
2266    
2267                if (this.treeTable == null) {
2268                    this.treeTable = treeTable;
2269                    // commented because still has issus
2270    //                bindRollover();
2271                }
2272                else {
2273                    throw new IllegalArgumentException("renderer already bound");
2274                }
2275            }
2276    
2277            /**
2278             * Install rollover support.
2279             * Not used - still has issues.
2280             * - not bidi-compliant
2281             * - no coordinate transformation for hierarchical column != 0
2282             * - method name clash enabled
2283             * - keyboard triggered click unreliable (triggers the treetable)
2284             * ...
2285             */
2286            @SuppressWarnings("unused")
2287            private void bindRollover() {
2288                setRolloverEnabled(treeTable.isRolloverEnabled());
2289                treeTable.addPropertyChangeListener(getRolloverListener());
2290            }
2291    
2292            
2293            /**
2294             * @return
2295             */
2296            private PropertyChangeListener getRolloverListener() {
2297                if (rolloverListener == null) {
2298                    rolloverListener = createRolloverListener();
2299                }
2300                return rolloverListener;
2301            }
2302    
2303            /**
2304             * Creates and returns a property change listener for 
2305             * table's rollover related properties. 
2306             * 
2307             * This implementation 
2308             * - Synchs the tree's rolloverEnabled 
2309             * - maps rollover cell from the table to the cell 
2310             *   (still incomplete: first column only)
2311             * 
2312             * @return
2313             */
2314            protected PropertyChangeListener createRolloverListener() {
2315                PropertyChangeListener l = new PropertyChangeListener() {
2316    
2317                    public void propertyChange(PropertyChangeEvent evt) {
2318                        if ((treeTable == null) || (treeTable != evt.getSource()))
2319                            return;
2320                        if ("rolloverEnabled".equals(evt.getPropertyName())) {
2321                            setRolloverEnabled(((Boolean) evt.getNewValue()).booleanValue());
2322                        }
2323                        if (RolloverProducer.ROLLOVER_KEY.equals(evt.getPropertyName())){
2324                            rollover(evt);
2325                        } 
2326                    }
2327    
2328                    private void rollover(PropertyChangeEvent evt) {
2329                        boolean isHierarchical = isHierarchical((Point)evt.getNewValue());
2330                        putClientProperty(evt.getPropertyName(), isHierarchical ? 
2331                               new Point((Point) evt.getNewValue()) : null);
2332                    }
2333                    
2334                    private boolean isHierarchical(Point point) {
2335                        if (point != null) {
2336                            int column = point.x;
2337                            if (column >= 0) {
2338                                return treeTable.isHierarchical(column);
2339                            }
2340                        }
2341                       return false;
2342                    }
2343                    @SuppressWarnings("unused")
2344                    Point rollover = new Point(-1, -1);
2345                };
2346                return l;
2347            }
2348    
2349            /**
2350             * {@inheritDoc} <p>
2351             * 
2352             * Overridden to produce clicked client props only. The
2353             * rollover are produced by a propertyChangeListener to 
2354             * the table's corresponding prop.
2355             * 
2356             */
2357            @Override
2358            protected RolloverProducer createRolloverProducer() {
2359                return new RolloverProducer() {
2360    
2361                    /**
2362                     * Overridden to do nothing.
2363                     * 
2364                     * @param e
2365                     * @param property
2366                     */
2367                    @Override
2368                    protected void updateRollover(MouseEvent e, String property, boolean fireAlways) {
2369                        if (CLICKED_KEY.equals(property)) {
2370                            super.updateRollover(e, property, fireAlways);
2371                        }
2372                    }
2373                    @Override
2374                    protected void updateRolloverPoint(JComponent component,
2375                            Point mousePoint) {
2376                        JXTree tree = (JXTree) component;
2377                        int row = tree.getClosestRowForLocation(mousePoint.x, mousePoint.y);
2378                        Rectangle bounds = tree.getRowBounds(row);
2379                        if (bounds == null) {
2380                            row = -1;
2381                        } else {
2382                            if ((bounds.y + bounds.height < mousePoint.y) || 
2383                                    bounds.x > mousePoint.x)   {
2384                                   row = -1;
2385                               }
2386                        }
2387                        int col = row < 0 ? -1 : 0;
2388                        rollover.x = col;
2389                        rollover.y = row;
2390                    }
2391                    
2392                };
2393            }
2394    
2395            
2396            @Override
2397            public void scrollRectToVisible(Rectangle aRect) {
2398                treeTable.scrollRectToVisible(aRect);
2399            }
2400    
2401            @Override
2402            protected void setExpandedState(TreePath path, boolean state) {
2403                treeTable.getTreeTableHacker().completeEditing();
2404                super.setExpandedState(path, state);
2405                treeTable.getTreeTableHacker().expansionChanged();
2406                
2407            }
2408    
2409            /**
2410             * updateUI is overridden to set the colors of the Tree's renderer
2411             * to match that of the table.
2412             */
2413            @Override
2414            public void updateUI() {
2415                super.updateUI();
2416                // Make the tree's cell renderer use the table's cell selection
2417                // colors.
2418                // TODO JW: need to revisit...
2419                // a) the "real" of a JXTree is always wrapped into a DelegatingRenderer
2420                //  consequently the if-block never executes
2421                // b) even if it does it probably (?) should not 
2422                // unconditionally overwrite custom selection colors. 
2423                // Check for UIResources instead. 
2424                TreeCellRenderer tcr = getCellRenderer();
2425                if (tcr instanceof DefaultTreeCellRenderer) {
2426                    DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer) tcr);
2427                    // For 1.1 uncomment this, 1.2 has a bug that will cause an
2428                    // exception to be thrown if the border selection color is null.
2429                    dtcr.setBorderSelectionColor(null);
2430                    dtcr.setTextSelectionColor(
2431                        UIManager.getColor("Table.selectionForeground"));
2432                    dtcr.setBackgroundSelectionColor(
2433                        UIManager.getColor("Table.selectionBackground"));
2434                }
2435            }
2436    
2437            /**
2438             * Sets the row height of the tree, and forwards the row height to
2439             * the table.
2440             * 
2441             *
2442             */
2443            @Override
2444            public void setRowHeight(int rowHeight) {
2445                // JW: can't ... updateUI invoked with rowHeight = 0
2446                // hmmm... looks fishy ...
2447    //            if (rowHeight <= 0) throw 
2448    //               new IllegalArgumentException("the rendering tree must have a fixed rowHeight > 0");
2449                super.setRowHeight(rowHeight);
2450                if (rowHeight > 0) {
2451                    if (treeTable != null) {
2452                        treeTable.adjustTableRowHeight(rowHeight);
2453                    }
2454                }
2455            }
2456    
2457    
2458            /**
2459             * This is overridden to set the height to match that of the JTable.
2460             */
2461            @Override
2462            public void setBounds(int x, int y, int w, int h) {
2463                if (treeTable != null) {
2464                    y = 0;
2465                    // It is not enough to set the height to treeTable.getHeight()
2466                    h = treeTable.getRowCount() * this.getRowHeight();
2467    //                int hierarchicalC = treeTable.getHierarchicalColumn();
2468    //                if (hierarchicalC >= 0) {
2469    //                    TableColumn column = treeTable.getColumn(hierarchicalC);
2470    //                    w = Math.min(w, column.getWidth());
2471    //                }
2472                }
2473                super.setBounds(x, y, w, h);
2474            }
2475    
2476            /**
2477             * Sublcassed to translate the graphics such that the last visible row
2478             * will be drawn at 0,0.
2479             */
2480            @Override
2481            public void paint(Graphics g) {
2482                Rectangle cellRect = treeTable.getCellRect(visibleRow, 0, false);
2483                g.translate(0, -cellRect.y);
2484    
2485                hierarchicalColumnWidth = getWidth();
2486                super.paint(g);
2487    
2488                // Draw the Table border if we have focus.
2489                if (highlightBorder != null) {
2490                    // #170: border not drawn correctly
2491                    // JW: position the border to be drawn in translated area
2492                    // still not satifying in all cases...
2493                    // RG: Now it satisfies (at least for the row margins)
2494                    // Still need to make similar adjustments for column margins...
2495                    highlightBorder.paintBorder(this, g, 0, cellRect.y,
2496                            getWidth(), cellRect.height);
2497                }
2498            }
2499    
2500            public void doClick() {
2501                if ((getCellRenderer() instanceof RolloverRenderer)
2502                        && ((RolloverRenderer) getCellRenderer()).isEnabled()) {
2503                    ((RolloverRenderer) getCellRenderer()).doClick();
2504                }
2505                
2506            }
2507    
2508            public Component getTableCellRendererComponent(JTable table,
2509                Object value,
2510                boolean isSelected, boolean hasFocus, int row, int column) {
2511                assert table == treeTable;
2512                // JW: quick fix for the tooltip part of #794-swingx:
2513                // visual properties must be reset in each cycle.
2514                // reverted - otherwise tooltip per Highlighter doesn't work
2515                // 
2516    //            setToolTipText(null);
2517                
2518                if (isSelected) {
2519                    setBackground(table.getSelectionBackground());
2520                    setForeground(table.getSelectionForeground());
2521                }
2522                else {
2523                    setBackground(table.getBackground());
2524                   setForeground(table.getForeground());
2525                }
2526    
2527                highlightBorder = null;
2528                if (treeTable != null) {
2529                    if (treeTable.realEditingRow() == row &&
2530                        treeTable.getEditingColumn() == column) {
2531                    }
2532                    else if (hasFocus) {
2533                        highlightBorder = UIManager.getBorder(
2534                            "Table.focusCellHighlightBorder");
2535                    }
2536                }
2537                
2538                visibleRow = row;
2539    
2540                return this;
2541            }
2542    
2543            private class ClippedTreeCellRenderer extends DefaultXTreeCellRenderer 
2544                implements StringValue 
2545                {
2546                @SuppressWarnings("unused")
2547                private boolean inpainting;
2548                private String shortText;
2549                @Override
2550                public void paint(Graphics g) {
2551                    String fullText = super.getText();
2552            
2553                     shortText = SwingUtilities.layoutCompoundLabel(
2554                        this, g.getFontMetrics(), fullText, getIcon(),
2555                        getVerticalAlignment(), getHorizontalAlignment(),
2556                        getVerticalTextPosition(), getHorizontalTextPosition(),
2557                        getItemRect(itemRect), iconRect, textRect,
2558                        getIconTextGap());
2559    
2560                    /** TODO: setText is more heavyweight than we want in this
2561                     * situation. Make JLabel.text protected instead of private.
2562             */
2563    
2564                    try {
2565                        inpainting = true;
2566                        // TODO JW: don't - override getText to return the short version
2567                        // during painting
2568                        setText(shortText); // temporarily truncate text
2569                        super.paint(g);
2570                    } finally {
2571                        inpainting = false;
2572                        setText(fullText); // restore full text
2573                    }
2574                }
2575    
2576                
2577                private Rectangle getItemRect(Rectangle itemRect) {
2578                    getBounds(itemRect);
2579    //                LOG.info("rect" + itemRect);
2580                    itemRect.width = hierarchicalColumnWidth - itemRect.x;
2581                    return itemRect;
2582                }
2583    
2584                @Override
2585                public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
2586                    return super.getTreeCellRendererComponent(tree, getHierarchicalTableValue(value), sel, expanded, leaf,
2587                            row, hasFocus);
2588                }
2589    
2590    
2591                /**
2592                 * 
2593                 * @param node the node in the treeModel as passed into the TreeCellRenderer
2594                 * @return the corresponding value of the hierarchical cell in the TreeTableModel
2595                 */
2596                private Object getHierarchicalTableValue(Object node) {
2597                    Object val = node;
2598                    
2599                    if (treeTable != null) {
2600                        int treeColumn = treeTable.getTreeTableModel().getHierarchicalColumn();
2601                        Object o = null; 
2602                        if (treeColumn >= 0) {
2603                            // following is unreliable during a paint cycle
2604                            // somehow interferes with BasicTreeUIs painting cache
2605    //                        o = treeTable.getValueAt(row, treeColumn);
2606                            // ask the model - that's always okay
2607                            // might blow if the TreeTableModel is strict in
2608                            // checking the containment of the value and 
2609                            // this renderer is called for sizing with a prototype
2610                            o = treeTable.getTreeTableModel().getValueAt(node, treeColumn);
2611                        }
2612                        val = o;
2613                    }
2614                    return val;
2615                }
2616    
2617                /**
2618                 * {@inheritDoc} <p>
2619                 */
2620                public String getString(Object node) {
2621    //                int treeColumn = treeTable.getTreeTableModel().getHierarchicalColumn();
2622    //                if (treeColumn >= 0) {
2623    //                    return StringValues.TO_STRING.getString(treeTable.getTreeTableModel().getValueAt(value, treeColumn));
2624    //                }
2625                    return StringValues.TO_STRING.getString(getHierarchicalTableValue(node));
2626                }
2627    
2628                // Rectangles filled in by SwingUtilities.layoutCompoundLabel();
2629                private final Rectangle iconRect = new Rectangle();
2630                private final Rectangle textRect = new Rectangle();
2631                // Rectangle filled in by this.getItemRect();
2632                private final Rectangle itemRect = new Rectangle();
2633            }
2634    
2635            /** Border to draw around the tree, if this is non-null, it will
2636             * be painted. */
2637            protected Border highlightBorder = null;
2638            protected JXTreeTable treeTable = null;
2639            protected int visibleRow = 0;
2640    
2641            // A JXTreeTable may not have more than one hierarchical column
2642            private int hierarchicalColumnWidth = 0;
2643    
2644        }
2645    
2646        /**
2647         * Returns the adapter that knows how to access the component data model.
2648         * The component data adapter is used by filters, sorters, and highlighters.
2649         *
2650         * @return the adapter that knows how to access the component data model
2651         */
2652        @Override
2653        protected ComponentAdapter getComponentAdapter() {
2654            if (dataAdapter == null) {
2655                dataAdapter = new TreeTableDataAdapter(this); 
2656            }
2657            return dataAdapter;
2658        }
2659    
2660    
2661        protected static class TreeTableDataAdapter extends JXTable.TableAdapter {
2662            private final JXTreeTable table;
2663    
2664            /**
2665             * Constructs a <code>TreeTableDataAdapter</code> for the specified
2666             * target component.
2667             *
2668             * @param component the target component
2669             */
2670            public TreeTableDataAdapter(JXTreeTable component) {
2671                super(component);
2672                table = component;
2673            }
2674            
2675            public JXTreeTable getTreeTable() {
2676                return table;
2677            }
2678    
2679            /**
2680             * {@inheritDoc}
2681             */
2682            @Override
2683            public boolean isExpanded() {
2684                return table.isExpanded(row); 
2685            }
2686    
2687            /**
2688             * {@inheritDoc}
2689             */
2690            @Override
2691            public int getDepth() {
2692                return table.getPathForRow(row).getPathCount() - 1;
2693            }
2694            
2695            /**
2696             * {@inheritDoc}
2697             */
2698            @Override
2699            public boolean isLeaf() {
2700                // Issue #270-swingx: guard against invisible row
2701                TreePath path = table.getPathForRow(row);
2702                if (path != null) {
2703                    return table.getTreeTableModel().isLeaf(path.getLastPathComponent());
2704                }
2705                // JW: this is the same as BasicTreeUI.isLeaf. 
2706                // Shouldn't happen anyway because must be called for visible rows only.
2707                return true; 
2708            }
2709            /**
2710             *
2711             * @return true if the cell identified by this adapter displays hierarchical
2712             *      nodes; false otherwise
2713             */
2714            @Override
2715            public boolean isHierarchical() {
2716                return table.isHierarchical(column);
2717            }
2718    
2719            /**
2720             * {@inheritDoc} <p>
2721             * 
2722             * Overridden to fix #821-swingx: string rep of hierarchical column incorrect.
2723             * In this case we must delegate to the tree directly if hidden, the visible
2724             * is handled by the TreeTable itself.<p>
2725             * 
2726             * PENDING JW: revisit once we switch to really using a table renderer. 
2727             */
2728            @Override
2729            public String getFilteredStringAt(int row, int column) {
2730                if (table.getTreeTableModel().getHierarchicalColumn() == column) {
2731                    if (modelToView(column) < 0) {
2732                        // hidden hierarchical column, access directly
2733                        return table.getHierarchicalStringAt(row);
2734                    }
2735                }
2736                return super.getFilteredStringAt(row, column);
2737            }
2738            
2739            /**
2740             * {@inheritDoc} <p>
2741             * 
2742             * Overridden to fix #821-swingx: string rep of hierarchical column incorrect.
2743             * In this case we must delegate to the tree directly if hidden, the visible
2744             * is handled by the TreeTable itself.<p>
2745             * 
2746             * PENDING JW: revisit once we switch to really using a table renderer. 
2747             */
2748            @Override
2749            public String getStringAt(int row, int column) {
2750                if (table.getTreeTableModel().getHierarchicalColumn() == column) {
2751                    if (modelToView(column) < 0) {
2752                        // hidden hierarchical column, access directly
2753                        return table.getHierarchicalStringAt(row);
2754                    }
2755                }
2756                return super.getStringAt(row, column);
2757            }
2758            
2759        }
2760    
2761    }