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