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