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 }