001/*
002 * $Id: JXTree.java 4166 2012-02-15 15:21:04Z kleopatra $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 * 
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 * Lesser General Public License for more details.
016 * 
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 */
021
022package org.jdesktop.swingx;
023
024import java.applet.Applet;
025import java.awt.Color;
026import java.awt.Component;
027import java.awt.KeyboardFocusManager;
028import java.awt.Rectangle;
029import java.awt.Window;
030import java.awt.event.ActionEvent;
031import java.beans.PropertyChangeEvent;
032import java.beans.PropertyChangeListener;
033import java.util.Hashtable;
034import java.util.Vector;
035import java.util.logging.Logger;
036import java.util.regex.Pattern;
037
038import javax.swing.Action;
039import javax.swing.ActionMap;
040import javax.swing.CellEditor;
041import javax.swing.Icon;
042import javax.swing.JComponent;
043import javax.swing.JPopupMenu;
044import javax.swing.JTree;
045import javax.swing.KeyStroke;
046import javax.swing.SwingUtilities;
047import javax.swing.UIManager;
048import javax.swing.event.CellEditorListener;
049import javax.swing.event.ChangeEvent;
050import javax.swing.event.ChangeListener;
051import javax.swing.event.TreeModelEvent;
052import javax.swing.event.TreeModelListener;
053import javax.swing.plaf.basic.BasicTreeUI;
054import javax.swing.text.Position.Bias;
055import javax.swing.tree.DefaultTreeCellRenderer;
056import javax.swing.tree.TreeCellRenderer;
057import javax.swing.tree.TreeModel;
058import javax.swing.tree.TreeNode;
059import javax.swing.tree.TreePath;
060
061import org.jdesktop.beans.JavaBean;
062import org.jdesktop.swingx.decorator.ComponentAdapter;
063import org.jdesktop.swingx.decorator.CompoundHighlighter;
064import org.jdesktop.swingx.decorator.Highlighter;
065import org.jdesktop.swingx.plaf.UIAction;
066import org.jdesktop.swingx.plaf.UIDependent;
067import org.jdesktop.swingx.renderer.StringValue;
068import org.jdesktop.swingx.renderer.StringValues;
069import org.jdesktop.swingx.rollover.RolloverProducer;
070import org.jdesktop.swingx.rollover.RolloverRenderer;
071import org.jdesktop.swingx.rollover.TreeRolloverController;
072import org.jdesktop.swingx.rollover.TreeRolloverProducer;
073import org.jdesktop.swingx.search.SearchFactory;
074import org.jdesktop.swingx.search.Searchable;
075import org.jdesktop.swingx.search.TreeSearchable;
076import org.jdesktop.swingx.tree.DefaultXTreeCellEditor;
077import org.jdesktop.swingx.tree.DefaultXTreeCellRenderer;
078
079
080/**
081 * Enhanced Tree component with support for SwingX rendering, highlighting,
082 * rollover and search functionality.
083 * <p>
084 * 
085 * <h2>Rendering and Highlighting</h2>
086 * 
087 * As all SwingX collection views, a JXTree is a HighlighterClient (PENDING JW:
088 * formally define and implement, like in AbstractTestHighlighter), that is it
089 * provides consistent api to add and remove Highlighters which can visually
090 * decorate the rendering component.
091 * <p>
092 * 
093 * <pre><code>
094 * 
095 * JXTree tree = new JXTree(new FileSystemModel());
096 * // use system file icons and name to render
097 * tree.setCellRenderer(new DefaultTreeRenderer(IconValues.FILE_ICON, 
098 *      StringValues.FILE_NAME));
099 * // highlight condition: file modified after a date     
100 * HighlightPredicate predicate = new HighlightPredicate() {
101 *    public boolean isHighlighted(Component renderer,
102 *                     ComponentAdapter adapter) {
103 *       File file = getUserObject(adapter.getValue());
104 *       return file != null ? lastWeek < file.lastModified : false;
105 *    }
106 * };
107 * // highlight with foreground color 
108 * tree.addHighlighter(new ColorHighlighter(predicate, null, Color.RED);      
109 * 
110 * </code></pre>
111 * 
112 * <i>Note:</i> for full functionality, a DefaultTreeRenderer must be installed
113 * as TreeCellRenderer. This is not done by default, because there are
114 * unresolved issues when editing. PENDING JW: still? Check!
115 * 
116 * <i>Note:</i> to support the highlighting this implementation wraps the
117 * TreeCellRenderer set by client code with a DelegatingRenderer which applies
118 * the Highlighter after delegating the default configuration to the wrappee. As
119 * a side-effect, getCellRenderer does return the wrapper instead of the custom
120 * renderer. To access the latter, client code must call getWrappedCellRenderer.
121 * <p>
122 * <h2>Rollover</h2>
123 * 
124 * As all SwingX collection views, a JXTree supports per-cell rollover. If
125 * enabled, the component fires rollover events on enter/exit of a cell which by
126 * default is promoted to the renderer if it implements RolloverRenderer, that
127 * is simulates live behaviour. The rollover events can be used by client code
128 * as well, f.i. to decorate the rollover row using a Highlighter.
129 * 
130 * <pre><code>
131 * 
132 * JXTree tree = new JXTree();
133 * tree.setRolloverEnabled(true);
134 * tree.setCellRenderer(new DefaultTreeRenderer());
135 * tree.addHighlighter(new ColorHighlighter(HighlightPredicate.ROLLOVER_ROW, 
136 *      null, Color.RED);      
137 * 
138 * </code></pre>
139 * 
140 * 
141 * <h2>Search</h2>
142 * 
143 * As all SwingX collection views, a JXTree is searchable. A search action is
144 * registered in its ActionMap under the key "find". The default behaviour is to
145 * ask the SearchFactory to open a search component on this component. The
146 * default keybinding is retrieved from the SearchFactory, typically ctrl-f (or
147 * cmd-f for Mac). Client code can register custom actions and/or bindings as
148 * appropriate.
149 * <p>
150 * 
151 * JXTree provides api to vend a renderer-controlled String representation of
152 * cell content. This allows the Searchable and Highlighters to use WYSIWYM
153 * (What-You-See-Is-What-You-Match), that is pattern matching against the actual
154 * string as seen by the user.
155 * 
156 * <h2>Miscellaneous</h2>
157 * 
158 * <ul>
159 * <li> Improved usability for editing: guarantees that the tree is the
160 * focusOwner if editing terminated by user gesture and guards against data
161 * corruption if focusLost while editing
162 * <li> Access methods for selection colors, for consistency with JXTable,
163 * JXList
164 * <li> Convenience methods and actions to expand, collapse all nodes
165 * </ul>
166 * 
167 * @author Ramesh Gupta
168 * @author Jeanette Winzenburg
169 * 
170 * @see org.jdesktop.swingx.renderer.DefaultTreeRenderer
171 * @see org.jdesktop.swingx.renderer.ComponentProvider
172 * @see org.jdesktop.swingx.decorator.Highlighter
173 * @see org.jdesktop.swingx.decorator.HighlightPredicate
174 * @see org.jdesktop.swingx.search.SearchFactory
175 * @see org.jdesktop.swingx.search.Searchable
176 * 
177 */
178@JavaBean
179public class JXTree extends JTree {
180    @SuppressWarnings("unused")
181    private static final Logger LOG = Logger.getLogger(JXTree.class.getName());
182    
183    
184    /** Empty int array used in getSelectedRows(). */
185    private static final int[] EMPTY_INT_ARRAY = new int[0];
186    /** Empty TreePath used in getSelectedPath() if selection empty. */
187    private static final TreePath[] EMPTY_TREEPATH_ARRAY = new TreePath[0];
188
189    /** Collection of active Highlighters. */
190    protected CompoundHighlighter compoundHighlighter;
191    /** Listener to changes of Highlighters in collection. */
192    private ChangeListener highlighterChangeListener;
193
194    /** Wrapper around the installed renderer, needed to support Highlighters. */
195    private DelegatingRenderer delegatingRenderer;
196
197    /**
198     * The RolloverProducer used if rollover is enabled.
199     */
200    private RolloverProducer rolloverProducer;
201
202    /**
203     * The RolloverController used if rollover is enabled.
204     */
205    private TreeRolloverController<JXTree> linkController;
206    
207    private boolean overwriteIcons;
208    private Searchable searchable;
209    
210    // hacks around core focus issues around editing.
211    /**
212     * The propertyChangeListener responsible for terminating
213     * edits if focus lost.
214     */
215    private CellEditorRemover editorRemover;
216    /**
217     * The CellEditorListener responsible to force the 
218     * focus back to the tree after terminating edits.
219     */
220    private CellEditorListener editorListener;
221    
222    /** Color of selected foreground. Added for consistent api across collection components. */
223    private Color selectionForeground;
224    /** Color of selected background. Added for consistent api across collection components. */
225    private Color selectionBackground;
226    
227    
228    
229    /**
230     * Constructs a <code>JXTree</code> with a sample model. The default model
231     * used by this tree defines a leaf node as any node without children.
232     */
233    public JXTree() {
234        init();
235    }
236
237    /**
238     * Constructs a <code>JXTree</code> with each element of the specified array
239     * as the child of a new root node which is not displayed. By default, this
240     * tree defines a leaf node as any node without children.
241     *
242     * This version of the constructor simply invokes the super class version
243     * with the same arguments.
244     *
245     * @param value an array of objects that are children of the root.
246     */
247    public JXTree(Object[] value) {
248        super(value);
249        init();
250    }
251
252    /**
253     * Constructs a <code>JXTree</code> with each element of the specified
254     * Vector as the child of a new root node which is not displayed.
255     * By default, this tree defines a leaf node as any node without children.
256     *
257     * This version of the constructor simply invokes the super class version
258     * with the same arguments.
259     *
260     * @param value an Vector of objects that are children of the root.
261     */
262    public JXTree(Vector<?> value) {
263        super(value);
264        init();
265    }
266
267    /**
268     * Constructs a <code>JXTree</code> created from a Hashtable which does not
269     * display with root. Each value-half of the key/value pairs in the HashTable
270     * becomes a child of the new root node. By default, the tree defines a leaf
271     * node as any node without children.
272     *
273     * This version of the constructor simply invokes the super class version
274     * with the same arguments.
275     *
276     * @param value a Hashtable containing objects that are children of the root.
277     */
278    public JXTree(Hashtable<?, ?> value) {
279        super(value);
280        init();
281    }
282
283    /**
284     * Constructs a <code>JXTree</code> with the specified TreeNode as its root,
285     * which displays the root node. By default, the tree defines a leaf node as
286     * any node without children.
287     *
288     * This version of the constructor simply invokes the super class version
289     * with the same arguments.
290     *
291     * @param root root node of this tree
292     */
293    public JXTree(TreeNode root) {
294        super(root, false);
295        init();
296    }
297
298    /**
299     * Constructs a <code>JXTree</code> with the specified TreeNode as its root,
300     * which displays the root node and which decides whether a node is a leaf
301     * node in the specified manner.
302     *
303     * This version of the constructor simply invokes the super class version
304     * with the same arguments.
305     *
306     * @param root root node of this tree
307     * @param asksAllowsChildren if true, only nodes that do not allow children
308     * are leaf nodes; otherwise, any node without children is a leaf node;
309     * @see javax.swing.tree.DefaultTreeModel#asksAllowsChildren
310     */
311    public JXTree(TreeNode root, boolean asksAllowsChildren) {
312        super(root, asksAllowsChildren);
313        init();
314    }
315
316    /**
317     * Constructs an instance of <code>JXTree</code> which displays the root
318     * node -- the tree is created using the specified data model.
319     * 
320     * This version of the constructor simply invokes the super class version
321     * with the same arguments.
322     * 
323     * @param newModel
324     *            the <code>TreeModel</code> to use as the data model
325     */
326    public JXTree(TreeModel newModel) {
327        super(newModel);
328        init();
329    }
330
331    /**
332     * Instantiates JXTree state which is new compared to super. Installs the
333     * Delegating renderer and editor, registers actions and keybindings.
334     * 
335     * This must be called from each constructor.
336     */
337    private void init() {
338        // Issue #1061-swingx: renderer inconsistencies
339        // force setting of renderer
340        setCellRenderer(createDefaultCellRenderer());
341        // Issue #233-swingx: default editor not bidi-compliant 
342        // manually install an enhanced TreeCellEditor which 
343        // behaves slightly better in RtoL orientation.
344        // Issue #231-swingx: icons lost
345        // Anyway, need to install the editor manually because
346        // the default install in BasicTreeUI doesn't know about
347        // the DelegatingRenderer and therefore can't see
348        // the DefaultTreeCellRenderer type to delegate to. 
349        // As a consequence, the icons are lost in the default
350        // setup.
351        // JW PENDING need to mimic ui-delegate default re-set?
352        // JW PENDING alternatively, cleanup and use DefaultXXTreeCellEditor in incubator
353        if (getWrappedCellRenderer() instanceof DefaultTreeCellRenderer) {
354            setCellEditor(new DefaultXTreeCellEditor(this, (DefaultTreeCellRenderer) getWrappedCellRenderer()));
355        }
356        // Register the actions that this class can handle.
357        ActionMap map = getActionMap();
358        map.put("expand-all", new Actions("expand-all"));
359        map.put("collapse-all", new Actions("collapse-all"));
360        map.put("find", createFindAction());
361
362        KeyStroke findStroke = SearchFactory.getInstance().getSearchAccelerator();
363        getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(findStroke, "find");
364    }
365
366    /**
367     * Listens to the model and updates the {@code expandedState} accordingly
368     * when nodes are removed, or changed.
369     * <p>
370     * This class will expand an invisible root when a child has been added to
371     * it.
372     * 
373     * @author Karl George Schaefer
374     */
375    protected class XTreeModelHandler extends TreeModelHandler {
376        /**
377         * {@inheritDoc}
378         */
379        @Override
380        public void treeNodesInserted(TreeModelEvent e) {
381            TreePath path = e.getTreePath();
382            
383            //fixes SwingX bug #612
384            if (path.getParentPath() == null && !isRootVisible() && isCollapsed(path)) {
385                //should this be wrapped in SwingUtilities.invokeLater?
386                expandPath(path);
387            }
388            
389            super.treeNodesInserted(e);
390        }
391    }
392    
393    /**
394     * {@inheritDoc}
395     */
396    @Override
397    protected TreeModelListener createTreeModelListener() {
398        return new XTreeModelHandler();
399    }
400
401    /**
402     * A small class which dispatches actions.
403     * TODO: Is there a way that we can make this static?
404     */
405    private class Actions extends UIAction {
406        Actions(String name) {
407            super(name);
408        }
409
410        @Override
411        public void actionPerformed(ActionEvent evt) {
412            if ("expand-all".equals(getName())) {
413                expandAll();
414            }
415            else if ("collapse-all".equals(getName())) {
416                collapseAll();
417            }
418        }
419    }
420
421
422//-------------------- search support
423    
424    /**
425     * Creates and returns the action to invoke on a find request.
426     * 
427     * @return the action to invoke on a find request.
428     */
429    private Action createFindAction() {
430        return new UIAction("find") {
431            @Override
432            public void actionPerformed(ActionEvent e) {
433                doFind();
434            }
435        };
436    }
437
438    /**
439     * Starts a search on this Tree's visible nodes. This implementation asks the
440     * SearchFactory to open a find widget on itself.
441     */
442    protected void doFind() {
443        SearchFactory.getInstance().showFindInput(this, getSearchable());
444    }
445
446    /**
447     * Returns a Searchable for this component, guaranteed to be not null. This 
448     * implementation lazily creates a TreeSearchable if necessary.
449     *  
450     * 
451     * @return a not-null Searchable for this component.
452     * 
453     * @see #setSearchable(Searchable)
454     * @see org.jdesktop.swingx.search.TreeSearchable
455     */
456    public Searchable getSearchable() {
457        if (searchable == null) {
458            searchable = new TreeSearchable(this);
459        }
460        return searchable;
461    }
462
463    /**
464     * Sets the Searchable for this component. If null, a default 
465     * Searchable will be created and used.
466     * 
467     * @param searchable the Searchable to use for this component, may be null to 
468     *   indicate using the default.
469     * 
470     * @see #getSearchable()
471     */
472    public void setSearchable(Searchable searchable) {
473        this.searchable = searchable;
474    }
475    
476    /**
477     * Returns the string representation of the cell value at the given position. 
478     * 
479     * @param row the row index of the cell in view coordinates
480     * @return the string representation of the cell value as it will appear in the 
481     *   table. 
482     */
483    public String getStringAt(int row) {
484        return getStringAt(getPathForRow(row));
485    }
486
487    /**
488     * Returns the string representation of the cell value at the given position. 
489     * 
490     * @param path the TreePath representing the node.
491     * @return the string representation of the cell value as it will appear in the 
492     *   table, or null if the path is not visible. 
493     */
494    public String getStringAt(TreePath path) {
495        if (path == null) return null;
496        TreeCellRenderer renderer = getDelegatingRenderer().getDelegateRenderer();
497        if (renderer instanceof StringValue) {
498            return ((StringValue) renderer).getString(path.getLastPathComponent());
499        }
500        return StringValues.TO_STRING.getString(path.getLastPathComponent());
501    }
502
503    
504    /**
505     * Overridden to respect the string representation, if any. This takes over 
506     * completely (as compared to super), internally messaging the Searchable.
507     * <p>
508     * 
509     * PENDING JW: re-visit once we support deep node search.
510     * 
511     */
512    @Override
513    public TreePath getNextMatch(String prefix, int startingRow, Bias bias) {
514        Pattern pattern = Pattern.compile("^" + prefix, Pattern.CASE_INSENSITIVE);
515        int row = getSearchable().search(pattern, startingRow, bias ==Bias.Backward);
516        return getPathForRow(row);
517    }
518
519//--------------------- misc. new api and super overrides
520    /**
521     * Collapses all nodes in this tree.
522     */
523    public void collapseAll() {
524        for (int i = getRowCount() - 1; i >= 0 ; i--) {
525            collapseRow(i);
526        }
527    }
528
529    /**
530     * Expands all nodes in this tree.<p>
531     * 
532     * Note: it's not recommended to use this method on the EDT for large/deep trees
533     * because expansion can take a considerable amount of time. 
534     */
535    public void expandAll() {
536        if (getRowCount() == 0) {
537            expandRoot();
538        }
539        for (int i = 0; i < getRowCount(); i++) {
540            expandRow(i);
541        }
542    }
543
544    /**
545     * Expands the root path if a TreeModel has been set, does nothing if not.
546     * 
547     */
548    private void expandRoot() {
549        TreeModel model = getModel();
550        if (model != null && model.getRoot() != null) {
551            expandPath(new TreePath(model.getRoot()));
552        }
553    }
554
555    /**
556     * {@inheritDoc}
557     * <p>
558     * 
559     * Overridden to always return a not-null array (following SwingX
560     * convention).
561     */
562    @Override
563    public int[] getSelectionRows() {
564        int[] rows = super.getSelectionRows();
565        return rows != null ? rows : EMPTY_INT_ARRAY; 
566    }
567    
568    /**
569     * {@inheritDoc}
570     * <p>
571     * 
572     * Overridden to always return a not-null array (following SwingX
573     * convention).
574     */
575    @Override
576    public TreePath[] getSelectionPaths() {
577        TreePath[] paths = super.getSelectionPaths();
578        return paths != null ? paths : EMPTY_TREEPATH_ARRAY; 
579    }
580
581    /**
582     * Returns the background color for selected cells.
583     *
584     * @return the <code>Color</code> used for the background of
585     * selected list items
586     * @see #setSelectionBackground
587     * @see #setSelectionForeground
588     */
589    public Color getSelectionBackground() {
590        return selectionBackground;
591    }
592    
593    /**
594     * Returns the selection foreground color.
595     *
596     * @return the <code>Color</code> object for the foreground property
597     * @see #setSelectionForeground
598     * @see #setSelectionBackground
599     */
600    public Color getSelectionForeground() {
601        return selectionForeground;
602    }
603   
604    /**
605     * Sets the foreground color for selected cells.  Cell renderers
606     * can use this color to render text and graphics for selected
607     * cells.
608     * <p>
609     * The default value of this property is defined by the look
610     * and feel implementation.
611     * <p>
612     * This is a JavaBeans bound property.
613     *
614     * @param selectionForeground  the <code>Color</code> to use in the foreground
615     *                             for selected list items
616     * @see #getSelectionForeground
617     * @see #setSelectionBackground
618     * @see #setForeground
619     * @see #setBackground
620     * @see #setFont
621     * @beaninfo
622     *       bound: true
623     *   attribute: visualUpdate true
624     * description: The foreground color of selected cells.
625     */
626    public void setSelectionForeground(Color selectionForeground) {
627        Object oldValue = getSelectionForeground();
628        this.selectionForeground = selectionForeground;
629        firePropertyChange("selectionForeground", oldValue, getSelectionForeground());
630        repaint();
631    }
632
633    /**
634     * Sets the background color for selected cells.  Cell renderers
635     * can use this color to the fill selected cells.
636     * <p>
637     * The default value of this property is defined by the look
638     * and feel implementation.
639     * <p>
640     * This is a JavaBeans bound property.
641     *
642     * @param selectionBackground  the <code>Color</code> to use for the 
643     *                             background of selected cells
644     * @see #getSelectionBackground
645     * @see #setSelectionForeground
646     * @see #setForeground
647     * @see #setBackground
648     * @see #setFont
649     * @beaninfo
650     *       bound: true
651     *   attribute: visualUpdate true
652     * description: The background color of selected cells.
653     */
654    public void setSelectionBackground(Color selectionBackground) {
655        Object oldValue = getSelectionBackground();
656        this.selectionBackground = selectionBackground;
657        firePropertyChange("selectionBackground", oldValue, getSelectionBackground());
658        repaint();
659    }
660
661    
662//------------------------- update ui 
663    
664    /**
665     * {@inheritDoc} <p>
666     * 
667     * Overridden to update selection background/foreground. Mimicking behaviour of 
668     * ui-delegates for JTable, JList.
669     */
670    @Override
671    public void updateUI() {
672        uninstallSelectionColors();
673        super.updateUI();
674        installSelectionColors();
675        updateHighlighterUI();
676        updateRendererEditorUI();
677        invalidateCellSizeCache();
678    }
679
680    
681    /**
682     * Quick fix for #1060-swingx: icons lost on toggling LAF
683     */
684    protected void updateRendererEditorUI() {
685        if (getCellEditor() instanceof UIDependent) {
686            ((UIDependent) getCellEditor()).updateUI();
687        }
688        // PENDING JW: here we get the DelegationRenderer which is not (yet) UIDependent
689        // need to think about how to handle the per-tree icons
690        // anyway, the "real" renderer usually is updated accidentally 
691        // don't know exactly why, added to the comp hierarchy?
692//        if (getCellRenderer() instanceof UIDependent) {
693//            ((UIDependent) getCellRenderer()).updateUI();
694//        }
695    }
696
697    /**
698     * Installs selection colors from UIManager. <p>
699     * 
700     * <b>Note:</b> this should be done in the UI delegate.
701     */
702    private void installSelectionColors() {
703        if (SwingXUtilities.isUIInstallable(getSelectionBackground())) {
704            setSelectionBackground(UIManager.getColor("Tree.selectionBackground"));
705        }
706        if (SwingXUtilities.isUIInstallable(getSelectionForeground())) {
707            setSelectionForeground(UIManager.getColor("Tree.selectionForeground"));
708        }
709        
710    }
711
712    /**
713     * Uninstalls selection colors. <p>
714     * 
715     * <b>Note:</b> this should be done in the UI delegate.
716     */
717    private void uninstallSelectionColors() {
718        if (SwingXUtilities.isUIInstallable(getSelectionBackground())) {
719            setSelectionBackground(null);
720        }
721        if (SwingXUtilities.isUIInstallable(getSelectionForeground())) {
722            setSelectionForeground(null);
723        }
724    }
725
726    /**
727     * Updates highlighter after <code>updateUI</code> changes.
728     * 
729     * @see org.jdesktop.swingx.plaf.UIDependent
730     */
731    protected void updateHighlighterUI() {
732        if (compoundHighlighter == null) return;
733        compoundHighlighter.updateUI();
734    }
735
736
737
738//------------------------ Rollover support
739    
740    /**
741     * Sets the property to enable/disable rollover support. If enabled, the list
742     * fires property changes on per-cell mouse rollover state, i.e. 
743     * when the mouse enters/leaves a list cell. <p>
744     * 
745     * This can be enabled to show "live" rollover behaviour, f.i. the cursor over a cell 
746     * rendered by a JXHyperlink.<p>
747     * 
748     * The default value is false.
749     * 
750     * @param rolloverEnabled a boolean indicating whether or not the rollover
751     *   functionality should be enabled.
752     * 
753     * @see #isRolloverEnabled()
754     * @see #getLinkController()
755     * @see #createRolloverProducer()
756     * @see org.jdesktop.swingx.rollover.RolloverRenderer  
757     */
758    public void setRolloverEnabled(boolean rolloverEnabled) {
759        boolean old = isRolloverEnabled();
760        if (rolloverEnabled == old) return;
761        if (rolloverEnabled) {
762            rolloverProducer = createRolloverProducer();
763            rolloverProducer.install(this);
764            getLinkController().install(this);
765        } else {
766            rolloverProducer.release(this);
767            rolloverProducer = null;
768            getLinkController().release();
769        }
770        firePropertyChange("rolloverEnabled", old, isRolloverEnabled());
771    }
772
773    /**
774     * Returns a boolean indicating whether or not rollover support is enabled. 
775     *
776     * @return a boolean indicating whether or not rollover support is enabled. 
777     * 
778     * @see #setRolloverEnabled(boolean)
779     */
780    public boolean isRolloverEnabled() {
781        return rolloverProducer != null;
782    }
783    
784    /**
785     * Returns the RolloverController for this component. Lazyly creates the 
786     * controller if necessary, that is the return value is guaranteed to be 
787     * not null. <p>
788     * 
789     * PENDING JW: rename to getRolloverController
790     * 
791     * @return the RolloverController for this tree, guaranteed to be not null.
792     * 
793     * @see #setRolloverEnabled(boolean)
794     * @see #createLinkController()
795     * @see org.jdesktop.swingx.rollover.RolloverController
796     */
797    protected TreeRolloverController<JXTree> getLinkController() {
798        if (linkController == null) {
799            linkController = createLinkController();
800        }
801        return linkController;
802    }
803
804    /**
805     * Creates and returns a RolloverController appropriate for this tree.
806     * 
807     * @return a RolloverController appropriate for this tree.
808     * 
809     * @see #getLinkController()
810     * @see org.jdesktop.swingx.rollover.RolloverController
811     */
812    protected TreeRolloverController<JXTree> createLinkController() {
813        return new TreeRolloverController<JXTree>();
814    }
815
816    /**
817     * Creates and returns the RolloverProducer to use with this tree.
818     * <p>
819     * 
820     * @return <code>RolloverProducer</code> to use with this tree
821     * 
822     * @see #setRolloverEnabled(boolean)
823     */
824    protected RolloverProducer createRolloverProducer() {
825        return new TreeRolloverProducer();
826    }
827
828  
829//----------------------- Highlighter api
830    
831    /**
832     * Sets the <code>Highlighter</code>s to the table, replacing any old settings.
833     * None of the given Highlighters must be null.<p>
834     * 
835     * This is a bound property. <p> 
836     * 
837     * Note: as of version #1.257 the null constraint is enforced strictly. To remove
838     * all highlighters use this method without param.
839     * 
840     * @param highlighters zero or more not null highlighters to use for renderer decoration.
841     * @throws NullPointerException if array is null or array contains null values.
842     * 
843     * @see #getHighlighters()
844     * @see #addHighlighter(Highlighter)
845     * @see #removeHighlighter(Highlighter)
846     * 
847     */
848    public void setHighlighters(Highlighter... highlighters) {
849        Highlighter[] old = getHighlighters();
850        getCompoundHighlighter().setHighlighters(highlighters);
851        firePropertyChange("highlighters", old, getHighlighters());
852    }
853
854    /**
855     * Returns the <code>Highlighter</code>s used by this table.
856     * Maybe empty, but guarantees to be never null.
857     * 
858     * @return the Highlighters used by this table, guaranteed to never null.
859     * @see #setHighlighters(Highlighter[])
860     */
861    public Highlighter[] getHighlighters() {
862        return getCompoundHighlighter().getHighlighters();
863    }
864    
865    /**
866     * Appends a <code>Highlighter</code> to the end of the list of used
867     * <code>Highlighter</code>s. The argument must not be null. 
868     * <p>
869     * 
870     * @param highlighter the <code>Highlighter</code> to add, must not be null.
871     * @throws NullPointerException if <code>Highlighter</code> is null.
872     * 
873     * @see #removeHighlighter(Highlighter)
874     * @see #setHighlighters(Highlighter[])
875     */
876    public void addHighlighter(Highlighter highlighter) {
877        Highlighter[] old = getHighlighters();
878        getCompoundHighlighter().addHighlighter(highlighter);
879        firePropertyChange("highlighters", old, getHighlighters());
880    }
881
882    /**
883     * Removes the given Highlighter. <p>
884     * 
885     * Does nothing if the Highlighter is not contained.
886     * 
887     * @param highlighter the Highlighter to remove.
888     * @see #addHighlighter(Highlighter)
889     * @see #setHighlighters(Highlighter...)
890     */
891    public void removeHighlighter(Highlighter highlighter) {
892        Highlighter[] old = getHighlighters();
893        getCompoundHighlighter().removeHighlighter(highlighter);
894        firePropertyChange("highlighters", old, getHighlighters());
895    }
896    
897    /**
898     * Returns the CompoundHighlighter assigned to the table, null if none.
899     * PENDING: open up for subclasses again?.
900     * 
901     * @return the CompoundHighlighter assigned to the table.
902     */
903    protected CompoundHighlighter getCompoundHighlighter() {
904        if (compoundHighlighter == null) {
905            compoundHighlighter = new CompoundHighlighter();
906            compoundHighlighter.addChangeListener(getHighlighterChangeListener());
907        }
908        return compoundHighlighter;
909    }
910
911    /**
912     * Returns the <code>ChangeListener</code> to use with highlighters. Lazily 
913     * creates the listener.
914     * 
915     * @return the ChangeListener for observing changes of highlighters, 
916     *   guaranteed to be <code>not-null</code>
917     */
918    protected ChangeListener getHighlighterChangeListener() {
919        if (highlighterChangeListener == null) {
920            highlighterChangeListener = createHighlighterChangeListener();
921        }
922        return highlighterChangeListener;
923    }
924
925    /**
926     * Creates and returns the ChangeListener observing Highlighters.
927     * <p>
928     * Here: repaints the table on receiving a stateChanged.
929     * 
930     * @return the ChangeListener defining the reaction to changes of
931     *         highlighters.
932     */
933    protected ChangeListener createHighlighterChangeListener() {
934        return new ChangeListener() {
935            @Override
936            public void stateChanged(ChangeEvent e) {
937                repaint();
938            }
939        };
940    }
941    
942    /**
943     * Sets the Icon to use for the handle of an expanded node.<p>
944     * 
945     * Note: this will only succeed if the current ui delegate is
946     * a BasicTreeUI otherwise it will do nothing.<p>
947     * 
948     * PENDING JW: incomplete api (no getter) and not a bound property.
949     * 
950     * @param expandedIcon the Icon to use for the handle of an expanded node.
951     */
952    public void setExpandedIcon(Icon expandedIcon) {
953        if (getUI() instanceof BasicTreeUI) {
954            ((BasicTreeUI) getUI()).setExpandedIcon(expandedIcon);
955        }
956    }
957    
958    /**
959     * Sets the Icon to use for the handle of a collapsed node.
960     * 
961     * Note: this will only succeed if the current ui delegate is
962     * a BasicTreeUI otherwise it will do nothing.
963     *  
964     * PENDING JW: incomplete api (no getter) and not a bound property.
965     * 
966     * @param collapsedIcon the Icon to use for the handle of a collapsed node.
967     */
968    public void setCollapsedIcon(Icon collapsedIcon) {
969        if (getUI() instanceof BasicTreeUI) {
970            ((BasicTreeUI) getUI()).setCollapsedIcon(collapsedIcon);
971        }
972    }
973    
974    /**
975     * Sets the Icon to use for a leaf node.<p>
976     * 
977     * Note: this will only succeed if current renderer is a 
978     * DefaultTreeCellRenderer.<p>
979     * 
980     * PENDING JW: this (all setXXIcon) is old api pulled up from the JXTreeTable. 
981     * Need to review if we really want it - problematic if sharing the same
982     * renderer instance across different trees.
983     * 
984     * PENDING JW: incomplete api (no getter) and not a bound property.<p>
985     * 
986     * @param leafIcon the Icon to use for a leaf node.
987     */
988    public void setLeafIcon(Icon leafIcon) {
989        getDelegatingRenderer().setLeafIcon(leafIcon);
990    }
991    
992    /**
993     * Sets the Icon to use for an open folder node.
994     * 
995     * Note: this will only succeed if current renderer is a 
996     * DefaultTreeCellRenderer.
997     * 
998     * PENDING JW: incomplete api (no getter) and not a bound property.
999     * 
1000     * @param openIcon the Icon to use for an open folder node.
1001     */
1002    public void setOpenIcon(Icon openIcon) {
1003        getDelegatingRenderer().setOpenIcon(openIcon);
1004    }
1005    
1006    /**
1007     * Sets the Icon to use for a closed folder node.
1008     * 
1009     * Note: this will only succeed if current renderer is a 
1010     * DefaultTreeCellRenderer.
1011     * 
1012     * PENDING JW: incomplete api (no getter) and not a bound property.
1013     * 
1014     * @param closedIcon the Icon to use for a closed folder node.
1015     */
1016    public void setClosedIcon(Icon closedIcon) {
1017        getDelegatingRenderer().setClosedIcon(closedIcon);
1018    }
1019    
1020    /**
1021     * Property to control whether per-tree icons should be 
1022     * copied to the renderer on setCellRenderer. <p>
1023     * 
1024     * The default value is false.
1025     * 
1026     * PENDING: should update the current renderer's icons when 
1027     * setting to true?
1028     * 
1029     * @param overwrite a boolean to indicate if the per-tree Icons should
1030     *   be copied to the new renderer on setCellRenderer.
1031     * 
1032     * @see #isOverwriteRendererIcons()  
1033     * @see #setLeafIcon(Icon)
1034     * @see #setOpenIcon(Icon)
1035     * @see #setClosedIcon(Icon)  
1036     */
1037    public void setOverwriteRendererIcons(boolean overwrite) {
1038        if (overwriteIcons == overwrite) return;
1039        boolean old = overwriteIcons;
1040        this.overwriteIcons = overwrite;
1041        firePropertyChange("overwriteRendererIcons", old, overwrite);
1042    }
1043
1044    /**
1045     * Returns a boolean indicating whether the per-tree icons should be 
1046     * copied to the renderer on setCellRenderer.
1047     * 
1048     * @return true if a TreeCellRenderer's icons will be overwritten with the
1049     *   tree's Icons, false if the renderer's icons will be unchanged.
1050     *   
1051     * @see #setOverwriteRendererIcons(boolean)
1052     * @see #setLeafIcon(Icon)
1053     * @see #setOpenIcon(Icon)
1054     * @see #setClosedIcon(Icon)  
1055     *     
1056     */
1057    public boolean isOverwriteRendererIcons() {
1058        return overwriteIcons;
1059    }
1060    
1061    private DelegatingRenderer getDelegatingRenderer() {
1062        if (delegatingRenderer == null) {
1063            // only called once... to get hold of the default?
1064            delegatingRenderer = new DelegatingRenderer();
1065        }
1066        return delegatingRenderer;
1067    }
1068
1069    /**
1070     * Creates and returns the default cell renderer to use. Subclasses may
1071     * override to use a different type.
1072     * <p>
1073     * 
1074     * This implementation returns a renderer of type
1075     * <code>DefaultTreeCellRenderer</code>. <b>Note:</b> Will be changed to
1076     * return a renderer of type <code>DefaultTreeRenderer</code>,
1077     * once WrappingProvider is reasonably stable.
1078     * 
1079     * @return the default cell renderer to use with this tree.
1080     */
1081    protected TreeCellRenderer createDefaultCellRenderer() {
1082//        return new DefaultTreeCellRenderer();
1083        return new DefaultXTreeCellRenderer();
1084    }
1085
1086    /**
1087     * {@inheritDoc} <p>
1088     * 
1089     * Overridden to return the delegating renderer which is wrapped around the
1090     * original to support highlighting. The returned renderer is of type 
1091     * DelegatingRenderer and guaranteed to not-null<p>
1092     * 
1093     * @see #setCellRenderer(TreeCellRenderer)
1094     * @see DelegatingRenderer
1095     */
1096    @Override
1097    public TreeCellRenderer getCellRenderer() {
1098        // PENDING JW: something wrong here - why exactly can't we return super? 
1099        // not even if we force the initial setting in init?
1100//        return super.getCellRenderer();
1101        return getDelegatingRenderer();
1102    }
1103
1104    /**
1105     * Returns the renderer installed by client code or the default if none has
1106     * been set.
1107     * 
1108     * @return the wrapped renderer.
1109     * @see #setCellRenderer(TreeCellRenderer)
1110     */
1111    public TreeCellRenderer getWrappedCellRenderer() {
1112        return getDelegatingRenderer().getDelegateRenderer();
1113    }
1114
1115    /**
1116     * {@inheritDoc} <p>
1117     * 
1118     * Overridden to wrap the given renderer in a DelegatingRenderer to support
1119     * highlighting. <p>
1120     * 
1121     * Note: the wrapping implies that the renderer returned from the getCellRenderer
1122     * is <b>not</b> the renderer as given here, but the wrapper. To access the original,
1123     * use <code>getWrappedCellRenderer</code>.
1124     * 
1125     * @see #getWrappedCellRenderer()
1126     * @see #getCellRenderer()
1127     */
1128    @Override
1129    public void setCellRenderer(TreeCellRenderer renderer) {
1130        // PENDING: do something against recursive setting
1131        // == multiple delegation...
1132        getDelegatingRenderer().setDelegateRenderer(renderer);
1133        super.setCellRenderer(delegatingRenderer);
1134        // quick hack for #1061: renderer/editor inconsistent
1135        if ((renderer instanceof DefaultTreeCellRenderer) && 
1136                (getCellEditor() instanceof DefaultXTreeCellEditor)) {
1137           ((DefaultXTreeCellEditor) getCellEditor()).setRenderer((DefaultTreeCellRenderer) renderer); 
1138        }
1139        firePropertyChange("cellRenderer", null, delegatingRenderer);
1140    }
1141
1142    
1143    /**
1144     * A decorator for the original TreeCellRenderer. Needed to hook highlighters
1145     * after messaging the delegate.<p>
1146     * 
1147     * PENDING JW: formally implement UIDependent? 
1148     * PENDING JW: missing updateUI anyway (got lost when c&p from JXList ;-)
1149     * PENDING JW: missing override of updateUI in xtree ...
1150     */
1151    public class DelegatingRenderer implements TreeCellRenderer, RolloverRenderer {
1152        private Icon    closedIcon = null;
1153        private Icon    openIcon = null;
1154        private Icon    leafIcon = null;
1155       
1156        private TreeCellRenderer delegate;
1157        
1158        /**
1159         * Instantiates a DelegatingRenderer with tree's default renderer as delegate.
1160         */
1161        public DelegatingRenderer() {
1162            this(null);
1163            initIcons(new DefaultTreeCellRenderer());
1164        }
1165
1166        /**
1167         * Instantiates a DelegatingRenderer with the given delegate. If the
1168         * delegate is null, the default is created via the list's factory method.
1169         * 
1170         * @param delegate the delegate to use, if null the tree's default is
1171         *   created and used.
1172         */
1173        public DelegatingRenderer(TreeCellRenderer delegate) {
1174            initIcons((DefaultTreeCellRenderer) (delegate instanceof DefaultTreeCellRenderer ? 
1175                    delegate : new DefaultTreeCellRenderer()));
1176            setDelegateRenderer(delegate);
1177        }
1178
1179        /**
1180         * initially sets the icons to the defaults as given
1181         * by a DefaultTreeCellRenderer.
1182         * 
1183         * @param renderer
1184         */
1185        private void initIcons(DefaultTreeCellRenderer renderer) {
1186            closedIcon = renderer.getDefaultClosedIcon();
1187            openIcon = renderer.getDefaultOpenIcon();
1188            leafIcon = renderer.getDefaultLeafIcon();
1189        }
1190
1191        /**
1192         * Sets the delegate. If the
1193         * delegate is null, the default is created via the list's factory method.
1194         * Updates the folder/leaf icons. 
1195         * 
1196         * THINK: how to update? always override with this.icons, only
1197         * if renderer's icons are null, update this icons if they are not,
1198         * update all if only one is != null.... ??
1199         * 
1200         * @param delegate the delegate to use, if null the list's default is
1201         *   created and used.
1202         */
1203        public void setDelegateRenderer(TreeCellRenderer delegate) {
1204            if (delegate == null) {
1205                delegate = createDefaultCellRenderer();
1206            }
1207            this.delegate = delegate;
1208            updateIcons();
1209        }
1210        
1211        /**
1212         * tries to set the renderers icons. Can succeed only if the
1213         * delegate is a DefaultTreeCellRenderer.
1214         * THINK: how to update? always override with this.icons, only
1215         * if renderer's icons are null, update this icons if they are not,
1216         * update all if only one is != null.... ??
1217         * 
1218         */
1219        private void updateIcons() {
1220            if (!isOverwriteRendererIcons()) return;
1221            setClosedIcon(closedIcon);
1222            setOpenIcon(openIcon);
1223            setLeafIcon(leafIcon);
1224        }
1225
1226        public void setClosedIcon(Icon closedIcon) {
1227            if (delegate instanceof DefaultTreeCellRenderer) {
1228                ((DefaultTreeCellRenderer) delegate).setClosedIcon(closedIcon);
1229            }
1230            this.closedIcon = closedIcon;
1231        }
1232        
1233        public void setOpenIcon(Icon openIcon) {
1234            if (delegate instanceof DefaultTreeCellRenderer) {
1235                ((DefaultTreeCellRenderer) delegate).setOpenIcon(openIcon);
1236            }
1237            this.openIcon = openIcon;
1238        }
1239        
1240        public void setLeafIcon(Icon leafIcon) {
1241            if (delegate instanceof DefaultTreeCellRenderer) {
1242                ((DefaultTreeCellRenderer) delegate).setLeafIcon(leafIcon);
1243            }
1244            this.leafIcon = leafIcon;
1245        }
1246        
1247        //--------------- TreeCellRenderer
1248        
1249        /**
1250         * Returns the delegate.
1251         * 
1252         * @return the delegate renderer used by this renderer, guaranteed to
1253         *   not-null.
1254         */
1255        public TreeCellRenderer getDelegateRenderer() {
1256            return delegate;
1257        }
1258        
1259        /**
1260         * {@inheritDoc} <p>
1261         * 
1262         * Overridden to apply the highlighters, if any, after calling the delegate.
1263         * The decorators are not applied if the row is invalid.
1264         */
1265        @Override
1266        public Component getTreeCellRendererComponent(JTree tree, Object value,
1267                boolean selected, boolean expanded, boolean leaf, int row,
1268                boolean hasFocus) {
1269            Component result = delegate.getTreeCellRendererComponent(tree,
1270                    value, selected, expanded, leaf, row, hasFocus);
1271
1272            if ((compoundHighlighter != null) && (row < getRowCount())
1273                    && (row >= 0)) {
1274                result = compoundHighlighter.highlight(result,
1275                        getComponentAdapter(row));
1276            } 
1277            
1278            return result;
1279        }
1280            
1281            // ------------------ RolloverRenderer
1282
1283        @Override
1284        public boolean isEnabled() {
1285            return (delegate instanceof RolloverRenderer)
1286                    && ((RolloverRenderer) delegate).isEnabled();
1287        }
1288            
1289        @Override
1290        public void doClick() {
1291            if (isEnabled()) {
1292                ((RolloverRenderer) delegate).doClick();
1293            }
1294        }
1295
1296    }
1297
1298    /**
1299     * Invalidates cell size caching in the ui delegate. May do nothing if there's no
1300     * safe (i.e. without reflection) way to message the delegate. <p>
1301     * 
1302     * This implementation calls BasicTreeUI setLeftChildIndent with the old indent if available. 
1303     * Beware: clearing the cache is an undocumented implementation side-effect of the 
1304     * method. Revisit if we ever should have a custom ui delegate.
1305     * 
1306     * 
1307     */
1308    public void invalidateCellSizeCache() {
1309        if (getUI() instanceof BasicTreeUI) {
1310            BasicTreeUI ui = (BasicTreeUI) getUI();
1311            ui.setLeftChildIndent(ui.getLeftChildIndent());
1312        }
1313    }
1314    
1315//----------------------- edit
1316    
1317    /**
1318     * {@inheritDoc} <p>
1319     * Overridden to fix focus issues with editors. 
1320     * This method installs and updates the internal CellEditorRemover which
1321     * terminates ongoing edits if appropriate. Additionally, it
1322     * registers a CellEditorListener with the cell editor to grab the 
1323     * focus back to tree, if appropriate.
1324     * 
1325     * @see #updateEditorRemover()
1326     */
1327    @Override
1328    public void startEditingAtPath(TreePath path) {
1329        super.startEditingAtPath(path);
1330        if (isEditing()) {
1331            updateEditorListener();
1332            updateEditorRemover();
1333        }
1334    }
1335
1336    
1337    /**
1338     * Hack to grab focus after editing.
1339     */
1340    private void updateEditorListener() {
1341        if (editorListener == null) {
1342            editorListener = new CellEditorListener() {
1343
1344                @Override
1345                public void editingCanceled(ChangeEvent e) {
1346                    terminated(e);
1347                }
1348
1349                /**
1350                 * @param e
1351                 */
1352                private void terminated(ChangeEvent e) {
1353                    analyseFocus();
1354                    ((CellEditor) e.getSource()).removeCellEditorListener(editorListener);
1355                }
1356
1357                @Override
1358                public void editingStopped(ChangeEvent e) {
1359                    terminated(e);
1360                }
1361                
1362            };
1363        }
1364        getCellEditor().addCellEditorListener(editorListener);
1365
1366    }
1367
1368    /**
1369     * This is called from cell editor listener if edit terminated.
1370     * Trying to analyse if we should grab the focus back to the
1371     * tree after. Brittle ... we assume we are the first to 
1372     * get the event, so we can analyse the hierarchy before the
1373     * editing component is removed.
1374     */
1375    protected void analyseFocus() {
1376        if (isFocusOwnerDescending()) {
1377            requestFocusInWindow();
1378        }
1379    }
1380
1381
1382    /**
1383     * Returns a boolean to indicate if the current focus owner 
1384     * is descending from this table. 
1385     * Returns false if not editing, otherwise walks the focusOwner
1386     * hierarchy, taking popups into account. <p>
1387     * 
1388     * PENDING: copied from JXTable ... should be somewhere in a utility
1389     * class?
1390     * 
1391     * @return a boolean to indicate if the current focus
1392     *   owner is contained.
1393     */
1394    private boolean isFocusOwnerDescending() {
1395        if (!isEditing()) return false;
1396        Component focusOwner = 
1397            KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
1398        // PENDING JW: special casing to not fall through ... really wanted?
1399        if (focusOwner == null) return false;
1400        if (SwingXUtilities.isDescendingFrom(focusOwner, this)) return true;
1401        // same with permanent focus owner
1402        Component permanent = 
1403            KeyboardFocusManager.getCurrentKeyboardFocusManager().getPermanentFocusOwner();
1404        return SwingXUtilities.isDescendingFrom(permanent, this);
1405    }
1406
1407
1408
1409    /**
1410     * Overridden to release the CellEditorRemover, if any.
1411     */
1412    @Override
1413    public void removeNotify() {
1414        if (editorRemover != null) {
1415            editorRemover.release();
1416            editorRemover = null;
1417        }
1418        super.removeNotify();
1419    }
1420
1421    /**
1422     * Lazily creates and updates the internal CellEditorRemover.
1423     * 
1424     *
1425     */
1426    private void updateEditorRemover() {
1427        if (editorRemover == null) {
1428            editorRemover = new CellEditorRemover();
1429        }
1430        editorRemover.updateKeyboardFocusManager();
1431    }
1432
1433    /** This class tracks changes in the keyboard focus state. It is used
1434     * when the JXTree is editing to determine when to terminate the edit.
1435     * If focus switches to a component outside of the JXTree, but in the
1436     * same window, this will terminate editing. The exact terminate 
1437     * behaviour is controlled by the invokeStopEditing property.
1438     * 
1439     * @see javax.swing.JTree#setInvokesStopCellEditing(boolean)
1440     * 
1441     */
1442    public class CellEditorRemover implements PropertyChangeListener {
1443        /** the focusManager this is listening to. */
1444        KeyboardFocusManager focusManager;
1445
1446        public CellEditorRemover() {
1447            updateKeyboardFocusManager();
1448        }
1449
1450        /**
1451         * Updates itself to listen to the current KeyboardFocusManager. 
1452         *
1453         */
1454        public void updateKeyboardFocusManager() {
1455            KeyboardFocusManager current = KeyboardFocusManager.getCurrentKeyboardFocusManager();
1456            setKeyboardFocusManager(current);
1457        }
1458
1459        /**
1460         * stops listening.
1461         *
1462         */
1463        public void release() {
1464            setKeyboardFocusManager(null);
1465        }
1466        
1467        /**
1468         * Sets the focusManager this is listening to. 
1469         * Unregisters/registers itself from/to the old/new manager, 
1470         * respectively. 
1471         * 
1472         * @param current the KeyboardFocusManager to listen too.
1473         */
1474        private void setKeyboardFocusManager(KeyboardFocusManager current) {
1475            if (focusManager == current)
1476                return;
1477            KeyboardFocusManager old = focusManager;
1478            if (old != null) {
1479                old.removePropertyChangeListener("permanentFocusOwner", this);
1480            }
1481            focusManager = current;
1482            if (focusManager != null) {
1483                focusManager.addPropertyChangeListener("permanentFocusOwner",
1484                        this);
1485            }
1486
1487        }
1488        @Override
1489        public void propertyChange(PropertyChangeEvent ev) {
1490            if (!isEditing()) {
1491                return;
1492            }
1493
1494            Component c = focusManager.getPermanentFocusOwner();
1495            JXTree tree = JXTree.this;
1496            while (c != null) {
1497                if (c instanceof JPopupMenu) {
1498                    c = ((JPopupMenu) c).getInvoker();
1499                } else {
1500
1501                    if (c == tree) {
1502                        // focus remains inside the table
1503                        return;
1504                    } else if ((c instanceof Window) ||
1505                            (c instanceof Applet && c.getParent() == null)) {
1506                        if (c == SwingUtilities.getRoot(tree)) {
1507                            if (tree.getInvokesStopCellEditing()) {
1508                                tree.stopEditing();
1509                            }
1510                            if (tree.isEditing()) {
1511                                tree.cancelEditing();
1512                            }
1513                        }
1514                        break;
1515                    }
1516                    c = c.getParent();
1517                }
1518            }
1519        }
1520    }
1521
1522// ------------------ oldish String conversion api, no longer recommended
1523    
1524    /**
1525     * {@inheritDoc} <p>
1526     * 
1527     * Overridden to initialize the String conversion method of the model, if any.<p>
1528     * PENDING JW: remove - that is an outdated approach?
1529     */
1530    @Override
1531    public void setModel(TreeModel newModel) {
1532        super.setModel(newModel);
1533    }
1534
1535
1536    
1537//------------------------------- ComponentAdapter    
1538    /**
1539     * @return the unconfigured ComponentAdapter.
1540     */
1541    protected ComponentAdapter getComponentAdapter() {
1542        if (dataAdapter == null) {
1543            dataAdapter = new TreeAdapter(this);
1544        }
1545        return dataAdapter;
1546    }
1547
1548    /**
1549     * Convenience to access a configured ComponentAdapter.
1550     * Note: the column index of the configured adapter is always 0.
1551     * 
1552     * @param index the row index in view coordinates, must be valid.
1553     * @return the configured ComponentAdapter.
1554     */
1555    protected ComponentAdapter getComponentAdapter(int index) {
1556        ComponentAdapter adapter = getComponentAdapter();
1557        adapter.column = 0;
1558        adapter.row = index;
1559        return adapter;
1560    }
1561
1562    protected ComponentAdapter dataAdapter;
1563
1564    protected static class TreeAdapter extends ComponentAdapter {
1565        private final JXTree tree;
1566
1567        /**
1568         * Constructs a <code>TableCellRenderContext</code> for the specified
1569         * target component.
1570         *
1571         * @param component the target component
1572         */
1573        public TreeAdapter(JXTree component) {
1574            super(component);
1575            tree = component;
1576        }
1577        
1578        public JXTree getTree() {
1579            return tree;
1580        }
1581
1582        /**
1583         * {@inheritDoc}
1584         */
1585        @Override
1586        public boolean hasFocus() {
1587            return tree.isFocusOwner() && (tree.getLeadSelectionRow() == row);
1588        }
1589
1590        /**
1591         * {@inheritDoc}
1592         */
1593        @Override
1594        public Object getValueAt(int row, int column) {
1595            TreePath path = tree.getPathForRow(row);
1596            return path.getLastPathComponent();
1597        }
1598        
1599        /**
1600         * {@inheritDoc}
1601         */
1602        @Override
1603        public String getStringAt(int row, int column) {
1604            return tree.getStringAt(row);
1605        }
1606        
1607        /**
1608         * {@inheritDoc}
1609         */
1610        @Override
1611        public Rectangle getCellBounds() {
1612            return tree.getRowBounds(row);
1613        }
1614        
1615        /**
1616         * {@inheritDoc}
1617         */
1618        @Override
1619        public boolean isEditable() {
1620            //this is not as robust as JXTable; should it be? -- kgs
1621            return tree.isPathEditable(tree.getPathForRow(row));
1622        }
1623        
1624        /**
1625         * {@inheritDoc}
1626         */
1627        @Override
1628        public boolean isSelected() {
1629            return tree.isRowSelected(row);
1630        }
1631
1632        /**
1633         * {@inheritDoc}
1634         */
1635        @Override
1636        public boolean isExpanded() {
1637            return tree.isExpanded(row);
1638        }
1639
1640        /**
1641         * {@inheritDoc}
1642         */
1643        @Override
1644        public int getDepth() {
1645            return tree.getPathForRow(row).getPathCount() - 1;
1646        }
1647        
1648        /**
1649         * {@inheritDoc}
1650         */
1651        @Override
1652        public boolean isHierarchical() {
1653            return true;
1654        }
1655
1656        /**
1657         * {@inheritDoc}
1658         */
1659        @Override
1660        public boolean isLeaf() {
1661            return tree.getModel().isLeaf(getValue());
1662        }
1663
1664        /**
1665         * {@inheritDoc}
1666         */
1667        @Override
1668        public boolean isCellEditable(int row, int column) {
1669            return false;        /** TODO:  */
1670        }
1671    }
1672
1673
1674}