001    /*
002     * $Id: JXTree.java,v 1.21 2006/05/14 08:12:19 dmouse Exp $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     * 
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     * 
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    
022    package org.jdesktop.swingx;
023    
024    import java.awt.Component;
025    import java.awt.Cursor;
026    import java.awt.Point;
027    import java.awt.Rectangle;
028    import java.awt.event.ActionEvent;
029    import java.awt.event.MouseEvent;
030    import java.lang.reflect.Method;
031    import java.util.Hashtable;
032    import java.util.Vector;
033    import java.util.logging.Logger;
034    import java.util.regex.Matcher;
035    import java.util.regex.Pattern;
036    
037    import javax.swing.Action;
038    import javax.swing.ActionMap;
039    import javax.swing.Icon;
040    import javax.swing.JComponent;
041    import javax.swing.JTree;
042    import javax.swing.KeyStroke;
043    import javax.swing.event.ChangeEvent;
044    import javax.swing.event.ChangeListener;
045    import javax.swing.plaf.basic.BasicTreeUI;
046    import javax.swing.tree.DefaultTreeCellRenderer;
047    import javax.swing.tree.TreeCellRenderer;
048    import javax.swing.tree.TreeModel;
049    import javax.swing.tree.TreeNode;
050    import javax.swing.tree.TreePath;
051    
052    import org.jdesktop.swingx.decorator.ComponentAdapter;
053    import org.jdesktop.swingx.decorator.FilterPipeline;
054    import org.jdesktop.swingx.decorator.HighlighterPipeline;
055    
056    
057    /**
058     * JXTree.
059     *
060     * PENDING: support filtering/sorting.
061     * 
062     * @author Ramesh Gupta
063     * @author Jeanette Winzenburg
064     */
065    public class JXTree extends JTree {
066        private static final Logger LOG = Logger.getLogger(JXTree.class.getName());
067        private Method conversionMethod = null;
068        private final static Class[] methodSignature = new Class[] {Object.class};
069        private final static Object[] methodArgs = new Object[] {null};
070    
071        protected FilterPipeline filters;
072        protected HighlighterPipeline highlighters;
073        private ChangeListener highlighterChangeListener;
074    
075        private DelegatingRenderer delegatingRenderer;
076    
077        /**
078         * Mouse/Motion/Listener keeping track of mouse moved in
079         * cell coordinates.
080         */
081        private RolloverProducer rolloverProducer;
082    
083        /**
084         * RolloverController: listens to cell over events and
085         * repaints entered/exited rows.
086         */
087        private TreeRolloverController linkController;
088        private boolean overwriteIcons;
089        private Searchable searchable;
090        
091        
092        
093        /**
094         * Constructs a <code>JXTree</code> with a sample model. The default model
095         * used by this tree defines a leaf node as any node without children.
096         */
097        public JXTree() {
098        initActions();
099        }
100    
101        /**
102         * Constructs a <code>JXTree</code> with each element of the specified array
103         * as the child of a new root node which is not displayed. By default, this
104         * tree defines a leaf node as any node without children.
105         *
106         * This version of the constructor simply invokes the super class version
107         * with the same arguments.
108         *
109         * @param value an array of objects that are children of the root.
110         */
111        public JXTree(Object[] value) {
112            super(value);
113        initActions();
114        }
115    
116        /**
117         * Constructs a <code>JXTree</code> with each element of the specified
118         * Vector as the child of a new root node which is not displayed.
119         * By default, this tree defines a leaf node as any node without children.
120         *
121         * This version of the constructor simply invokes the super class version
122         * with the same arguments.
123         *
124         * @param value an Vector of objects that are children of the root.
125         */
126        public JXTree(Vector value) {
127            super(value);
128        initActions();
129        }
130    
131        /**
132         * Constructs a <code>JXTree</code> created from a Hashtable which does not
133         * display with root. Each value-half of the key/value pairs in the HashTable
134         * becomes a child of the new root node. By default, the tree defines a leaf
135         * node as any node without children.
136         *
137         * This version of the constructor simply invokes the super class version
138         * with the same arguments.
139         *
140         * @param value a Hashtable containing objects that are children of the root.
141         */
142        public JXTree(Hashtable value) {
143            super(value);
144        initActions();
145        }
146    
147        /**
148         * Constructs a <code>JXTree</code> with the specified TreeNode as its root,
149         * which displays the root node. By default, the tree defines a leaf node as
150         * any node without children.
151         *
152         * This version of the constructor simply invokes the super class version
153         * with the same arguments.
154         *
155         * @param root root node of this tree
156         */
157        public JXTree(TreeNode root) {
158            super(root, false);
159        }
160    
161        /**
162         * Constructs a <code>JXTree</code> with the specified TreeNode as its root,
163         * which displays the root node and which decides whether a node is a leaf
164         * node in the specified manner.
165         *
166         * This version of the constructor simply invokes the super class version
167         * with the same arguments.
168         *
169         * @param root root node of this tree
170         * @param asksAllowsChildren if true, only nodes that do not allow children
171         * are leaf nodes; otherwise, any node without children is a leaf node;
172         * @see javax.swing.tree.DefaultTreeModel#asksAllowsChildren
173         */
174        public JXTree(TreeNode root, boolean asksAllowsChildren) {
175            super(root, asksAllowsChildren);
176            initActions();
177        }
178    
179        /**
180         * Constructs an instance of <code>JXTree</code> which displays the root
181         * node -- the tree is created using the specified data model.
182         * 
183         * This version of the constructor simply invokes the super class version
184         * with the same arguments.
185         * 
186         * @param newModel
187         *            the <code>TreeModel</code> to use as the data model
188         */
189        public JXTree(TreeModel newModel) {
190            super(newModel);
191            initActions();
192            // To support delegation of convertValueToText() to the model...
193            // JW: need to set again (is done in setModel, but at call
194            // in super constructor the field is not yet valid)
195            conversionMethod = getValueConversionMethod(newModel);
196        }
197    
198        @Override
199        public void setModel(TreeModel newModel) {
200            // To support delegation of convertValueToText() to the model...
201            // JW: method needs to be set before calling super
202            // otherwise there are size caching problems
203            conversionMethod = getValueConversionMethod(newModel);
204            super.setModel(newModel);
205        }
206    
207        private Method getValueConversionMethod(TreeModel model) {
208            try {
209                return model == null ? null : model.getClass().getMethod(
210                        "convertValueToText", methodSignature);
211            } catch (NoSuchMethodException ex) {
212                LOG.finer("ex " + ex);
213                LOG.finer("no conversionMethod in " + model.getClass());
214            }
215            return null;
216        }
217    
218        @Override
219        public String convertValueToText(Object value, boolean selected,
220                boolean expanded, boolean leaf, int row, boolean hasFocus) {
221            // Delegate to model, if possible. Otherwise fall back to superclass...
222            if (value != null) {
223                if (conversionMethod == null) {
224                    return value.toString();
225                } else {
226                    try {
227                        methodArgs[0] = value;
228                        return (String) conversionMethod.invoke(getModel(),
229                                methodArgs);
230                    } catch (Exception ex) {
231                        LOG.finer("ex " + ex);
232                        LOG.finer("can't invoke " + conversionMethod);
233                    }
234                }
235            }
236            return "";
237        }
238    
239        private void initActions() {
240            // Register the actions that this class can handle.
241            ActionMap map = getActionMap();
242            map.put("expand-all", new Actions("expand-all"));
243            map.put("collapse-all", new Actions("collapse-all"));
244            map.put("find", createFindAction());
245            // JW: this should be handled by the LF!
246            KeyStroke findStroke = KeyStroke.getKeyStroke("control F");
247            getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(findStroke, "find");
248    
249        }
250    
251        /**
252         * A small class which dispatches actions.
253         * TODO: Is there a way that we can make this static?
254         */
255        private class Actions extends UIAction {
256            Actions(String name) {
257                super(name);
258            }
259    
260            public void actionPerformed(ActionEvent evt) {
261                if ("expand-all".equals(getName())) {
262            expandAll();
263                }
264                else if ("collapse-all".equals(getName())) {
265                    collapseAll();
266                }
267            }
268        }
269    
270    
271    //-------------------- search support
272        
273        private Action createFindAction() {
274            Action findAction = new UIAction("find") {
275    
276                public void actionPerformed(ActionEvent e) {
277                    doFind();
278                    
279                }
280                
281            };
282            return findAction;
283        }
284    
285        protected void doFind() {
286            SearchFactory.getInstance().showFindInput(this, getSearchable());
287        }
288    
289        /**
290         * 
291         * @return a not-null Searchable for this editor.
292         */
293        public Searchable getSearchable() {
294            if (searchable == null) {
295                searchable = new TreeSearchable();
296            }
297            return searchable;
298        }
299    
300        /**
301         * sets the Searchable for this editor. If null, a default 
302         * searchable will be used.
303         * 
304         * @param searchable
305         */
306        public void setSearchable(Searchable searchable) {
307            this.searchable = searchable;
308        }
309        
310     
311        /**
312         * A searchable targetting the visible rows of a JXTree.
313         * 
314         * PENDING: value to string conversion should behave as nextMatch (?) which
315         * uses the convertValueToString().
316         * 
317         */
318        public class TreeSearchable extends AbstractSearchable {
319    
320            protected void findMatchAndUpdateState(Pattern pattern, int startRow,
321                    boolean backwards) {
322                SearchResult searchResult = null;
323                if (backwards) {
324                    for (int index = startRow; index >= 0 && searchResult == null; index--) {
325                        searchResult = findMatchAt(pattern, index);
326                    }
327                } else {
328                    for (int index = startRow; index < getSize()
329                            && searchResult == null; index++) {
330                        searchResult = findMatchAt(pattern, index);
331                    }
332                }
333                updateState(searchResult);
334    
335            }
336    
337            protected SearchResult findExtendedMatch(Pattern pattern, int row) {
338                return findMatchAt(pattern, row);
339            }
340    
341            /**
342             * Matches the cell content at row/col against the given Pattern.
343             * Returns an appropriate SearchResult if matching or null if no
344             * matching
345             * 
346             * @param pattern
347             * @param row
348             *            a valid row index in view coordinates
349             *            a valid column index in view coordinates
350             * @return an appropriate <code>SearchResult</code> if matching or
351             * null if no matching
352             */
353            protected SearchResult findMatchAt(Pattern pattern, int row) {
354                TreePath path = getPathForRow(row);
355                Object value = null;
356                if (path != null) {
357                    value = path.getLastPathComponent();
358                }
359                if (value != null) {
360                    Matcher matcher = pattern.matcher(value.toString());
361                    if (matcher.find()) {
362                        return createSearchResult(matcher, row, -1);
363                    }
364                }
365                return null;
366            }
367    
368            protected int getSize() {
369                return getRowCount();
370            }
371    
372            protected void moveMatchMarker() {
373                int row = lastSearchResult.foundRow;
374                setSelectionRow(row);
375                if (row >= 0) {
376                    scrollRowToVisible(row);
377                }
378    
379            }
380    
381        }
382        
383        /**
384         * Collapses all nodes in the tree table.
385         */
386        public void collapseAll() {
387            for (int i = getRowCount() - 1; i >= 0 ; i--) {
388                collapseRow(i);
389            }
390        }
391    
392        /**
393         * Expands all nodes in the tree table.
394         */
395        public void expandAll() {
396            for (int i = 0; i < getRowCount(); i++) {
397                expandRow(i);
398            }
399        }
400    
401    
402        public HighlighterPipeline getHighlighters() {
403            return highlighters;
404        }
405    
406        /** Assigns a HighlighterPipeline to the table. */
407        public void setHighlighters(HighlighterPipeline pipeline) {
408            HighlighterPipeline old = getHighlighters();
409            if (old != null) {
410                old.removeChangeListener(getHighlighterChangeListener());
411            }
412            highlighters = pipeline;
413            if (highlighters != null) {
414                highlighters.addChangeListener(getHighlighterChangeListener());
415            }
416            firePropertyChange("highlighters", old, getHighlighters());
417        }
418    
419        private ChangeListener getHighlighterChangeListener() {
420            if (highlighterChangeListener == null) {
421                highlighterChangeListener = new ChangeListener() {
422    
423                    public void stateChanged(ChangeEvent e) {
424                        repaint();
425                        
426                    }
427                    
428                };
429            }
430            return highlighterChangeListener;
431        }
432    
433    
434        /**
435         * Property to enable/disable rollover support. This can be enabled
436         * to show "live" rollover behaviour, f.i. the cursor over LinkModel cells. 
437         * Default is disabled.
438         * @param rolloverEnabled
439         */
440        public void setRolloverEnabled(boolean rolloverEnabled) {
441            boolean old = isRolloverEnabled();
442            if (rolloverEnabled == old) return;
443            if (rolloverEnabled) {
444                rolloverProducer = createRolloverProducer();
445                addMouseListener(rolloverProducer);
446                addMouseMotionListener(rolloverProducer);
447                getLinkController().install(this);
448            } else {
449                removeMouseListener(rolloverProducer);
450                removeMouseMotionListener(rolloverProducer);
451                rolloverProducer = null;
452                getLinkController().release();
453            }
454            firePropertyChange("rolloverEnabled", old, isRolloverEnabled());
455        }
456    
457        protected TreeRolloverController getLinkController() {
458            if (linkController == null) {
459                linkController = createLinkController();
460            }
461            return linkController;
462        }
463    
464        protected TreeRolloverController createLinkController() {
465            return new TreeRolloverController();
466        }
467    
468        /**
469         * creates and returns the RolloverProducer to use with this tree.
470         * A "hit" for rollover is covering the total width of the tree.
471         * Additionally, a pressed to the right (but outside of the label bounds)
472         * is re-dispatched as a pressed just inside the label bounds. This 
473         * is a first go for #166-swingx.
474         * 
475         * @return <code>RolloverProducer</code> to use with this tree
476         */
477        protected RolloverProducer createRolloverProducer() {
478            RolloverProducer r = new RolloverProducer() {
479                
480                @Override
481                public void mousePressed(MouseEvent e) {
482                    JXTree tree = (JXTree) e.getComponent();
483                    Point mousePoint = e.getPoint();
484                  int labelRow = tree.getRowForLocation(mousePoint.x, mousePoint.y);
485                  // default selection
486                  if (labelRow >= 0) return;
487                  int row = tree.getClosestRowForLocation(mousePoint.x, mousePoint.y);
488                  Rectangle bounds = tree.getRowBounds(row);
489                  if (bounds == null) {
490                      row = -1;
491                  } else {
492                      if ((bounds.y + bounds.height < mousePoint.y) || 
493                           bounds.x > mousePoint.x)   {
494                          row = -1;
495                      }
496                  }
497                  // no hit
498                  if (row < 0) return;
499                  tree.dispatchEvent(new MouseEvent(tree, e.getID(), e.getWhen(), 
500                          e.getModifiers(), bounds.x + bounds.width - 2, mousePoint.y,
501                          e.getClickCount(), e.isPopupTrigger(), e.getButton()));
502                }
503    
504                protected void updateRolloverPoint(JComponent component,
505                        Point mousePoint) {
506                    JXTree tree = (JXTree) component;
507                    int row = tree.getClosestRowForLocation(mousePoint.x, mousePoint.y);
508                    Rectangle bounds = tree.getRowBounds(row);
509                    if (bounds == null) {
510                        row = -1;
511                    } else {
512                        if ((bounds.y + bounds.height < mousePoint.y) || 
513                                bounds.x > mousePoint.x)   {
514                               row = -1;
515                           }
516                    }
517                    int col = row < 0 ? -1 : 0;
518                    rollover.x = col;
519                    rollover.y = row;
520                }
521    
522            };
523            return r;
524        }
525    
526      
527       
528        /**
529         * returns the rolloverEnabled property.
530         *
531         * TODO: Why doesn't this just return rolloverEnabled???
532         *
533         * @return if rollober is enabled.
534         */
535        public boolean isRolloverEnabled() {
536            return rolloverProducer != null;
537        }
538    
539    
540        /**
541         * listens to rollover properties. 
542         * Repaints effected component regions.
543         * Updates link cursor.
544         * 
545         * @author Jeanette Winzenburg
546         */
547        public  class TreeRolloverController<T extends JTree>  extends RolloverController<T> {
548        
549            private Cursor oldCursor;
550            
551    //    -------------------------------------JTree rollover
552            
553            protected void rollover(Point oldLocation, Point newLocation) {
554                setRolloverCursor(newLocation);
555                //setLinkCursor(list, newLocation);
556                // JW: conditional repaint not working?
557                component.repaint();
558    //            if (oldLocation != null) {
559    //                Rectangle r = tree.getRowBounds(oldLocation.y);
560    ////                r.x = 0;
561    ////                r.width = table.getWidth();
562    //                if (r != null)
563    //                tree.repaint(r);
564    //            }
565    //            if (newLocation != null) {
566    //                Rectangle r = tree.getRowBounds(newLocation.y);
567    ////                r.x = 0;
568    ////                r.width = table.getWidth();
569    //                if (r != null)
570    //                tree.repaint(r);
571    //            }
572            }
573    
574    
575            private void setRolloverCursor(Point location) {
576                if (hasRollover(location)) {
577                    if (oldCursor == null) {
578                        oldCursor = component.getCursor();
579                        component.setCursor(Cursor
580                                .getPredefinedCursor(Cursor.HAND_CURSOR));
581                    }
582                } else {
583                    if (oldCursor != null) {
584                        component.setCursor(oldCursor);
585                        oldCursor = null;
586                    }
587                }
588    
589            }
590    
591    
592            protected RolloverRenderer getRolloverRenderer(Point location, boolean prepare) {
593                TreeCellRenderer renderer = component.getCellRenderer();
594                RolloverRenderer rollover = renderer instanceof RolloverRenderer 
595                    ? (RolloverRenderer) renderer : null;
596                if ((rollover != null) && !rollover.isEnabled()) {
597                    rollover = null;
598                }
599                if ((rollover != null) && prepare) {
600                    TreePath path = component.getPathForRow(location.y);
601                    Object element = path != null ? path.getLastPathComponent() : null;
602                    renderer.getTreeCellRendererComponent(component, element, false, 
603                            false, false, 
604                            location.y, false);
605                }
606                return rollover;
607            }
608    
609    
610            protected Point getFocusedCell() {
611                // TODO Auto-generated method stub
612                return null;
613            }
614    
615        }
616    
617    
618        
619        private DelegatingRenderer getDelegatingRenderer() {
620            if (delegatingRenderer == null) {
621                // only called once... to get hold of the default?
622                delegatingRenderer = new DelegatingRenderer();
623                delegatingRenderer.setDelegateRenderer(super.getCellRenderer());
624            }
625            return delegatingRenderer;
626        }
627    
628        public void setRolloverCursor(Point newLocation) {
629            // TODO Auto-generated method stub
630            
631        }
632    
633        public TreeCellRenderer getCellRenderer() {
634            return getDelegatingRenderer();
635        }
636    
637        public void setCellRenderer(TreeCellRenderer renderer) {
638            // PENDING: do something against recursive setting
639            // == multiple delegation...
640            getDelegatingRenderer().setDelegateRenderer(renderer);
641            super.setCellRenderer(delegatingRenderer);
642        }
643    
644        /**
645         * sets the icon for the handle of an expanded node.
646         * 
647         * Note: this will only succeed if the current ui delegate is
648         * a BasicTreeUI otherwise it will do nothing.
649         * 
650         * @param expanded
651         */
652        public void setExpandedIcon(Icon expanded) {
653            if (getUI() instanceof BasicTreeUI) {
654                ((BasicTreeUI) getUI()).setExpandedIcon(expanded);
655            }
656        }
657        
658        /**
659         * sets the icon for the handel of a collapsed node.
660         * 
661         * Note: this will only succeed if the current ui delegate is
662         * a BasicTreeUI otherwise it will do nothing.
663         *  
664         * @param collapsed
665         */
666        public void setCollapsedIcon(Icon collapsed) {
667            if (getUI() instanceof BasicTreeUI) {
668                ((BasicTreeUI) getUI()).setCollapsedIcon(collapsed);
669            }
670        }
671        
672        /**
673         * set the icon for a leaf node.
674         * 
675         * Note: this will only succeed if current renderer is a 
676         * DefaultTreeCellRenderer.
677         * 
678         * @param leafIcon
679         */
680        public void setLeafIcon(Icon leafIcon) {
681            getDelegatingRenderer().setLeafIcon(leafIcon);
682            
683        }
684        
685        /**
686         * set the icon for a open non-leaf node.
687         * 
688         * Note: this will only succeed if current renderer is a 
689         * DefaultTreeCellRenderer.
690         * 
691         * @param openIcon
692         */
693        public void setOpenIcon(Icon openIcon) {
694            getDelegatingRenderer().setOpenIcon(openIcon);
695        }
696        
697        /**
698         * set the icon for a closed non-leaf node.
699         * 
700         * Note: this will only succeed if current renderer is a 
701         * DefaultTreeCellRenderer.
702         * 
703         * @param closedIcon
704         */
705        public void setClosedIcon(Icon closedIcon) {
706            getDelegatingRenderer().setClosedIcon(closedIcon);
707        }
708        
709        /**
710         * Property to control whether per-tree icons should be 
711         * copied to the renderer on setCellRenderer.
712         * 
713         * the default is false for backward compatibility.
714         * 
715         * PENDING: should update the current renderer's icons when 
716         * setting to true?
717         * 
718         * @param overwrite
719         */
720        public void setOverwriteRendererIcons(boolean overwrite) {
721            if (overwriteIcons == overwrite) return;
722            boolean old = overwriteIcons;
723            this.overwriteIcons = overwrite;
724            firePropertyChange("overwriteRendererIcons", old, overwrite);
725        }
726    
727        public boolean isOverwriteRendererIcons() {
728            return overwriteIcons;
729        }
730        
731        public class DelegatingRenderer implements TreeCellRenderer, RolloverRenderer {
732            private Icon    closedIcon = null;
733            private Icon    openIcon = null;
734            private Icon    leafIcon = null;
735           
736            private TreeCellRenderer delegate;
737            
738            public DelegatingRenderer() {
739                initIcons(new DefaultTreeCellRenderer());
740            }
741    
742            /**
743             * initially sets the icons to the defaults as given
744             * by a DefaultTreeCellRenderer.
745             * 
746             * @param renderer
747             */
748            private void initIcons(DefaultTreeCellRenderer renderer) {
749                closedIcon = renderer.getDefaultClosedIcon();
750                openIcon = renderer.getDefaultOpenIcon();
751                leafIcon = renderer.getDefaultLeafIcon();
752            }
753    
754            /**
755             * Set the delegate renderer. 
756             * Updates the folder/leaf icons. 
757             * 
758             * THINK: how to update? always override with this.icons, only
759             * if renderer's icons are null, update this icons if they are not,
760             * update all if only one is != null.... ??
761             * 
762             * @param delegate
763             */
764            public void setDelegateRenderer(TreeCellRenderer delegate) {
765                if (delegate == null) {
766                    delegate = new DefaultTreeCellRenderer();
767                }
768                this.delegate = delegate;
769                updateIcons();
770            }
771            
772            /**
773             * tries to set the renderers icons. Can succeed only if the
774             * delegate is a DefaultTreeCellRenderer.
775             * THINK: how to update? always override with this.icons, only
776             * if renderer's icons are null, update this icons if they are not,
777             * update all if only one is != null.... ??
778             * 
779             */
780            private void updateIcons() {
781                if (!isOverwriteRendererIcons()) return;
782                setClosedIcon(closedIcon);
783                setOpenIcon(openIcon);
784                setLeafIcon(leafIcon);
785            }
786    
787            public void setClosedIcon(Icon closedIcon) {
788                if (delegate instanceof DefaultTreeCellRenderer) {
789                    ((DefaultTreeCellRenderer) delegate).setClosedIcon(closedIcon);
790                }
791                this.closedIcon = closedIcon;
792            }
793            
794            public void setOpenIcon(Icon openIcon) {
795                if (delegate instanceof DefaultTreeCellRenderer) {
796                    ((DefaultTreeCellRenderer) delegate).setOpenIcon(openIcon);
797                }
798                this.openIcon = openIcon;
799            }
800            
801            public void setLeafIcon(Icon leafIcon) {
802                if (delegate instanceof DefaultTreeCellRenderer) {
803                    ((DefaultTreeCellRenderer) delegate).setLeafIcon(leafIcon);
804                }
805                this.leafIcon = leafIcon;
806            }
807            
808            //--------------- TreeCellRenderer
809            
810            public TreeCellRenderer getDelegateRenderer() {
811                return delegate;
812            }
813                public Component getTreeCellRendererComponent(JTree tree, Object value, 
814                        boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
815                    Component result = delegate.getTreeCellRendererComponent(tree, value, 
816                            selected, expanded, leaf, row, hasFocus);
817    
818                        if (highlighters != null) {
819                            getComponentAdapter().row = row;
820                            result = highlighters.apply(result, getComponentAdapter());
821                        }
822    
823                     return result;
824                }
825                
826                //------------------ RolloverRenderer
827                
828                public boolean isEnabled() {
829                    return (delegate instanceof RolloverRenderer) && 
830                       ((RolloverRenderer) delegate).isEnabled();
831                }
832                
833                public void doClick() {
834                    if (isEnabled()) {
835                        ((RolloverRenderer) delegate).doClick();
836                    }
837                }
838    
839        }
840    
841        
842        protected ComponentAdapter getComponentAdapter() {
843            return dataAdapter;
844        }
845    
846        private final ComponentAdapter dataAdapter = new TreeAdapter(this);
847    
848        protected static class TreeAdapter extends ComponentAdapter {
849            private final JXTree tree;
850    
851            /**
852             * Constructs a <code>TableCellRenderContext</code> for the specified
853             * target component.
854             *
855             * @param component the target component
856             */
857            public TreeAdapter(JXTree component) {
858                super(component);
859                tree = component;
860            }
861            public JXTree getTree() {
862                return tree;
863            }
864    
865            public boolean hasFocus() {
866                return tree.isFocusOwner() && (tree.getLeadSelectionRow() == row);
867            }
868    
869            public Object getValueAt(int row, int column) {
870                TreePath path = tree.getPathForRow(row);
871                return path.getLastPathComponent();
872            }
873    
874            public Object getFilteredValueAt(int row, int column) {
875                /** TODO: Implement filtering */
876                return getValueAt(row, column);
877            }
878    
879            public boolean isSelected() {
880                return tree.isRowSelected(row);
881            }
882    
883            @Override
884            public boolean isExpanded() {
885                return tree.isExpanded(row);
886            }
887    
888            @Override
889            public boolean isLeaf() {
890                return tree.getModel().isLeaf(getValue());
891            }
892    
893            public boolean isCellEditable(int row, int column) {
894                return false;   /** TODO:  */
895            }
896    
897            public void setValueAt(Object aValue, int row, int column) {
898                /** TODO:  */
899            }
900            
901            public String getColumnName(int columnIndex) {
902                return "Column_" + columnIndex;
903            }
904            
905            public String getColumnIdentifier(int columnIndex) {
906                return null;
907            }
908        }
909    
910    }