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 }