1/* 2 * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package javax.swing.tree; 27 28import java.awt.Color; 29import java.awt.Component; 30import java.awt.Dimension; 31import java.awt.Font; 32import java.awt.Graphics; 33import java.awt.Insets; 34import java.awt.Rectangle; 35import javax.swing.plaf.ColorUIResource; 36import javax.swing.plaf.FontUIResource; 37import javax.swing.plaf.UIResource; 38import javax.swing.plaf.basic.BasicGraphicsUtils; 39import javax.swing.Icon; 40import javax.swing.JLabel; 41import javax.swing.JTree; 42import javax.swing.LookAndFeel; 43import javax.swing.UIManager; 44import javax.swing.border.EmptyBorder; 45import sun.swing.DefaultLookup; 46 47/** 48 * Displays an entry in a tree. 49 * <code>DefaultTreeCellRenderer</code> is not opaque and 50 * unless you subclass paint you should not change this. 51 * See <a 52 href="http://docs.oracle.com/javase/tutorial/uiswing/components/tree.html">How to Use Trees</a> 53 * in <em>The Java Tutorial</em> 54 * for examples of customizing node display using this class. 55 * <p> 56 * The set of icons and colors used by {@code DefaultTreeCellRenderer} 57 * can be configured using the various setter methods. The value for 58 * each property is initialized from the defaults table. When the 59 * look and feel changes ({@code updateUI} is invoked), any properties 60 * that have a value of type {@code UIResource} are refreshed from the 61 * defaults table. The following table lists the mapping between 62 * {@code DefaultTreeCellRenderer} property and defaults table key: 63 * 64 * <table class="striped"> 65 * <caption>Properties</caption> 66 * <thead> 67 * <tr> 68 * <th>Property: 69 * <th>Key: 70 * </tr> 71 * </thead> 72 * <tbody> 73 * <tr><td>"leafIcon"<td>"Tree.leafIcon" 74 * <tr><td>"closedIcon"<td>"Tree.closedIcon" 75 * <tr><td>"openIcon"<td>"Tree.openIcon" 76 * <tr><td>"textSelectionColor"<td>"Tree.selectionForeground" 77 * <tr><td>"textNonSelectionColor"<td>"Tree.textForeground" 78 * <tr><td>"backgroundSelectionColor"<td>"Tree.selectionBackground" 79 * <tr><td>"backgroundNonSelectionColor"<td>"Tree.textBackground" 80 * <tr><td>"borderSelectionColor"<td>"Tree.selectionBorderColor" 81 * </tbody> 82 * </table> 83 * <p> 84 * <strong><a id="override">Implementation Note:</a></strong> 85 * This class overrides 86 * <code>invalidate</code>, 87 * <code>validate</code>, 88 * <code>revalidate</code>, 89 * <code>repaint</code>, 90 * and 91 * <code>firePropertyChange</code> 92 * solely to improve performance. 93 * If not overridden, these frequently called methods would execute code paths 94 * that are unnecessary for the default tree cell renderer. 95 * If you write your own renderer, 96 * take care to weigh the benefits and 97 * drawbacks of overriding these methods. 98 * 99 * <p> 100 * <strong>Warning:</strong> 101 * Serialized objects of this class will not be compatible with 102 * future Swing releases. The current serialization support is 103 * appropriate for short term storage or RMI between applications running 104 * the same version of Swing. As of 1.4, support for long term storage 105 * of all JavaBeans™ 106 * has been added to the <code>java.beans</code> package. 107 * Please see {@link java.beans.XMLEncoder}. 108 * 109 * @author Rob Davis 110 * @author Ray Ryan 111 * @author Scott Violet 112 */ 113@SuppressWarnings("serial") // Same-version serialization only 114public class DefaultTreeCellRenderer extends JLabel implements TreeCellRenderer 115{ 116 /** Last tree the renderer was painted in. */ 117 private JTree tree; 118 119 /** Is the value currently selected. */ 120 protected boolean selected; 121 /** True if has focus. */ 122 protected boolean hasFocus; 123 /** True if draws focus border around icon as well. */ 124 private boolean drawsFocusBorderAroundIcon; 125 /** If true, a dashed line is drawn as the focus indicator. */ 126 private boolean drawDashedFocusIndicator; 127 128 // If drawDashedFocusIndicator is true, the following are used. 129 /** 130 * Background color of the tree. 131 */ 132 private Color treeBGColor; 133 /** 134 * Color to draw the focus indicator in, determined from the background. 135 * color. 136 */ 137 private Color focusBGColor; 138 139 // Icons 140 /** Icon used to show non-leaf nodes that aren't expanded. */ 141 protected transient Icon closedIcon; 142 143 /** Icon used to show leaf nodes. */ 144 protected transient Icon leafIcon; 145 146 /** Icon used to show non-leaf nodes that are expanded. */ 147 protected transient Icon openIcon; 148 149 // Colors 150 /** Color to use for the foreground for selected nodes. */ 151 protected Color textSelectionColor; 152 153 /** Color to use for the foreground for non-selected nodes. */ 154 protected Color textNonSelectionColor; 155 156 /** Color to use for the background when a node is selected. */ 157 protected Color backgroundSelectionColor; 158 159 /** Color to use for the background when the node isn't selected. */ 160 protected Color backgroundNonSelectionColor; 161 162 /** Color to use for the focus indicator when the node has focus. */ 163 protected Color borderSelectionColor; 164 165 private boolean isDropCell; 166 private boolean fillBackground; 167 168 /** 169 * Set to true after the constructor has run. 170 */ 171 private boolean inited; 172 173 /** 174 * Creates a {@code DefaultTreeCellRenderer}. Icons and text color are 175 * determined from the {@code UIManager}. 176 */ 177 public DefaultTreeCellRenderer() { 178 inited = true; 179 } 180 181 /** 182 * {@inheritDoc} 183 * 184 * @since 1.7 185 */ 186 public void updateUI() { 187 super.updateUI(); 188 // To avoid invoking new methods from the constructor, the 189 // inited field is first checked. If inited is false, the constructor 190 // has not run and there is no point in checking the value. As 191 // all look and feels have a non-null value for these properties, 192 // a null value means the developer has specifically set it to 193 // null. As such, if the value is null, this does not reset the 194 // value. 195 if (!inited || (getLeafIcon() instanceof UIResource)) { 196 setLeafIcon(DefaultLookup.getIcon(this, ui, "Tree.leafIcon")); 197 } 198 if (!inited || (getClosedIcon() instanceof UIResource)) { 199 setClosedIcon(DefaultLookup.getIcon(this, ui, "Tree.closedIcon")); 200 } 201 if (!inited || (getOpenIcon() instanceof UIResource)) { 202 setOpenIcon(DefaultLookup.getIcon(this, ui, "Tree.openIcon")); 203 } 204 if (!inited || (getTextSelectionColor() instanceof UIResource)) { 205 setTextSelectionColor( 206 DefaultLookup.getColor(this, ui, "Tree.selectionForeground")); 207 } 208 if (!inited || (getTextNonSelectionColor() instanceof UIResource)) { 209 setTextNonSelectionColor( 210 DefaultLookup.getColor(this, ui, "Tree.textForeground")); 211 } 212 if (!inited || (getBackgroundSelectionColor() instanceof UIResource)) { 213 setBackgroundSelectionColor( 214 DefaultLookup.getColor(this, ui, "Tree.selectionBackground")); 215 } 216 if (!inited || 217 (getBackgroundNonSelectionColor() instanceof UIResource)) { 218 setBackgroundNonSelectionColor( 219 DefaultLookup.getColor(this, ui, "Tree.textBackground")); 220 } 221 if (!inited || (getBorderSelectionColor() instanceof UIResource)) { 222 setBorderSelectionColor( 223 DefaultLookup.getColor(this, ui, "Tree.selectionBorderColor")); 224 } 225 drawsFocusBorderAroundIcon = DefaultLookup.getBoolean( 226 this, ui, "Tree.drawsFocusBorderAroundIcon", false); 227 drawDashedFocusIndicator = DefaultLookup.getBoolean( 228 this, ui, "Tree.drawDashedFocusIndicator", false); 229 230 fillBackground = DefaultLookup.getBoolean(this, ui, "Tree.rendererFillBackground", true); 231 Insets margins = DefaultLookup.getInsets(this, ui, "Tree.rendererMargins"); 232 if (margins != null) { 233 setBorder(new EmptyBorder(margins.top, margins.left, 234 margins.bottom, margins.right)); 235 } 236 237 setName("Tree.cellRenderer"); 238 } 239 240 241 /** 242 * Returns the default icon, for the current laf, that is used to 243 * represent non-leaf nodes that are expanded. 244 * 245 * @return the default icon, for the current laf, that is used to 246 * represent non-leaf nodes that are expanded. 247 */ 248 public Icon getDefaultOpenIcon() { 249 return DefaultLookup.getIcon(this, ui, "Tree.openIcon"); 250 } 251 252 /** 253 * Returns the default icon, for the current laf, that is used to 254 * represent non-leaf nodes that are not expanded. 255 * 256 * @return the default icon, for the current laf, that is used to 257 * represent non-leaf nodes that are not expanded. 258 */ 259 public Icon getDefaultClosedIcon() { 260 return DefaultLookup.getIcon(this, ui, "Tree.closedIcon"); 261 } 262 263 /** 264 * Returns the default icon, for the current laf, that is used to 265 * represent leaf nodes. 266 * 267 * @return the default icon, for the current laf, that is used to 268 * represent leaf nodes. 269 */ 270 public Icon getDefaultLeafIcon() { 271 return DefaultLookup.getIcon(this, ui, "Tree.leafIcon"); 272 } 273 274 /** 275 * Sets the icon used to represent non-leaf nodes that are expanded. 276 * 277 * @param newIcon the icon to be used for expanded non-leaf nodes 278 */ 279 public void setOpenIcon(Icon newIcon) { 280 openIcon = newIcon; 281 } 282 283 /** 284 * Returns the icon used to represent non-leaf nodes that are expanded. 285 * 286 * @return the icon used to represent non-leaf nodes that are expanded 287 */ 288 public Icon getOpenIcon() { 289 return openIcon; 290 } 291 292 /** 293 * Sets the icon used to represent non-leaf nodes that are not expanded. 294 * 295 * @param newIcon the icon to be used for not expanded non-leaf nodes 296 */ 297 public void setClosedIcon(Icon newIcon) { 298 closedIcon = newIcon; 299 } 300 301 /** 302 * Returns the icon used to represent non-leaf nodes that are not 303 * expanded. 304 * 305 * @return the icon used to represent non-leaf nodes that are not 306 * expanded 307 */ 308 public Icon getClosedIcon() { 309 return closedIcon; 310 } 311 312 /** 313 * Sets the icon used to represent leaf nodes. 314 * 315 * @param newIcon icon to be used for leaf nodes 316 */ 317 public void setLeafIcon(Icon newIcon) { 318 leafIcon = newIcon; 319 } 320 321 /** 322 * Returns the icon used to represent leaf nodes. 323 * 324 * @return the icon used to represent leaf nodes 325 */ 326 public Icon getLeafIcon() { 327 return leafIcon; 328 } 329 330 /** 331 * Sets the color the text is drawn with when the node is selected. 332 * 333 * @param newColor color to be used for text when the node is selected 334 */ 335 public void setTextSelectionColor(Color newColor) { 336 textSelectionColor = newColor; 337 } 338 339 /** 340 * Returns the color the text is drawn with when the node is selected. 341 * 342 * @return the color the text is drawn with when the node is selected 343 */ 344 public Color getTextSelectionColor() { 345 return textSelectionColor; 346 } 347 348 /** 349 * Sets the color the text is drawn with when the node isn't selected. 350 * 351 * @param newColor color to be used for text when the node isn't selected 352 */ 353 public void setTextNonSelectionColor(Color newColor) { 354 textNonSelectionColor = newColor; 355 } 356 357 /** 358 * Returns the color the text is drawn with when the node isn't selected. 359 * 360 * @return the color the text is drawn with when the node isn't selected. 361 */ 362 public Color getTextNonSelectionColor() { 363 return textNonSelectionColor; 364 } 365 366 /** 367 * Sets the color to use for the background if node is selected. 368 * 369 * @param newColor to be used for the background if the node is selected 370 */ 371 public void setBackgroundSelectionColor(Color newColor) { 372 backgroundSelectionColor = newColor; 373 } 374 375 376 /** 377 * Returns the color to use for the background if node is selected. 378 * 379 * @return the color to use for the background if node is selected 380 */ 381 public Color getBackgroundSelectionColor() { 382 return backgroundSelectionColor; 383 } 384 385 /** 386 * Sets the background color to be used for non selected nodes. 387 * 388 * @param newColor color to be used for the background for non selected nodes 389 */ 390 public void setBackgroundNonSelectionColor(Color newColor) { 391 backgroundNonSelectionColor = newColor; 392 } 393 394 /** 395 * Returns the background color to be used for non selected nodes. 396 * 397 * @return the background color to be used for non selected nodes. 398 */ 399 public Color getBackgroundNonSelectionColor() { 400 return backgroundNonSelectionColor; 401 } 402 403 /** 404 * Sets the color to use for the border. 405 * 406 * @param newColor color to be used for the border 407 */ 408 public void setBorderSelectionColor(Color newColor) { 409 borderSelectionColor = newColor; 410 } 411 412 /** 413 * Returns the color the border is drawn. 414 * 415 * @return the color the border is drawn 416 */ 417 public Color getBorderSelectionColor() { 418 return borderSelectionColor; 419 } 420 421 /** 422 * Subclassed to map <code>FontUIResource</code>s to null. If 423 * <code>font</code> is null, or a <code>FontUIResource</code>, this 424 * has the effect of letting the font of the JTree show 425 * through. On the other hand, if <code>font</code> is non-null, and not 426 * a <code>FontUIResource</code>, the font becomes <code>font</code>. 427 */ 428 public void setFont(Font font) { 429 if(font instanceof FontUIResource) 430 font = null; 431 super.setFont(font); 432 } 433 434 /** 435 * Gets the font of this component. 436 * @return this component's font; if a font has not been set 437 * for this component, the font of its parent is returned 438 */ 439 public Font getFont() { 440 Font font = super.getFont(); 441 442 if (font == null && tree != null) { 443 // Strive to return a non-null value, otherwise the html support 444 // will typically pick up the wrong font in certain situations. 445 font = tree.getFont(); 446 } 447 return font; 448 } 449 450 /** 451 * Subclassed to map <code>ColorUIResource</code>s to null. If 452 * <code>color</code> is null, or a <code>ColorUIResource</code>, this 453 * has the effect of letting the background color of the JTree show 454 * through. On the other hand, if <code>color</code> is non-null, and not 455 * a <code>ColorUIResource</code>, the background becomes 456 * <code>color</code>. 457 */ 458 public void setBackground(Color color) { 459 if(color instanceof ColorUIResource) 460 color = null; 461 super.setBackground(color); 462 } 463 464 /** 465 * Configures the renderer based on the passed in components. 466 * The value is set from messaging the tree with 467 * <code>convertValueToText</code>, which ultimately invokes 468 * <code>toString</code> on <code>value</code>. 469 * The foreground color is set based on the selection and the icon 470 * is set based on the <code>leaf</code> and <code>expanded</code> 471 * parameters. 472 */ 473 public Component getTreeCellRendererComponent(JTree tree, Object value, 474 boolean sel, 475 boolean expanded, 476 boolean leaf, int row, 477 boolean hasFocus) { 478 String stringValue = tree.convertValueToText(value, sel, 479 expanded, leaf, row, hasFocus); 480 481 this.tree = tree; 482 this.hasFocus = hasFocus; 483 setText(stringValue); 484 485 Color fg = null; 486 isDropCell = false; 487 488 JTree.DropLocation dropLocation = tree.getDropLocation(); 489 if (dropLocation != null 490 && dropLocation.getChildIndex() == -1 491 && tree.getRowForPath(dropLocation.getPath()) == row) { 492 493 Color col = DefaultLookup.getColor(this, ui, "Tree.dropCellForeground"); 494 if (col != null) { 495 fg = col; 496 } else { 497 fg = getTextSelectionColor(); 498 } 499 500 isDropCell = true; 501 } else if (sel) { 502 fg = getTextSelectionColor(); 503 } else { 504 fg = getTextNonSelectionColor(); 505 } 506 507 setForeground(fg); 508 509 Icon icon = null; 510 if (leaf) { 511 icon = getLeafIcon(); 512 } else if (expanded) { 513 icon = getOpenIcon(); 514 } else { 515 icon = getClosedIcon(); 516 } 517 518 if (!tree.isEnabled()) { 519 setEnabled(false); 520 LookAndFeel laf = UIManager.getLookAndFeel(); 521 Icon disabledIcon = laf.getDisabledIcon(tree, icon); 522 if (disabledIcon != null) icon = disabledIcon; 523 setDisabledIcon(icon); 524 } else { 525 setEnabled(true); 526 setIcon(icon); 527 } 528 setComponentOrientation(tree.getComponentOrientation()); 529 530 selected = sel; 531 532 return this; 533 } 534 535 /** 536 * Paints the value. The background is filled based on selected. 537 */ 538 public void paint(Graphics g) { 539 Color bColor; 540 541 if (isDropCell) { 542 bColor = DefaultLookup.getColor(this, ui, "Tree.dropCellBackground"); 543 if (bColor == null) { 544 bColor = getBackgroundSelectionColor(); 545 } 546 } else if (selected) { 547 bColor = getBackgroundSelectionColor(); 548 } else { 549 bColor = getBackgroundNonSelectionColor(); 550 if (bColor == null) { 551 bColor = getBackground(); 552 } 553 } 554 555 int imageOffset = -1; 556 if (bColor != null && fillBackground) { 557 imageOffset = getLabelStart(); 558 g.setColor(bColor); 559 if(getComponentOrientation().isLeftToRight()) { 560 g.fillRect(imageOffset, 0, getWidth() - imageOffset, 561 getHeight()); 562 } else { 563 g.fillRect(0, 0, getWidth() - imageOffset, 564 getHeight()); 565 } 566 } 567 568 if (hasFocus) { 569 if (drawsFocusBorderAroundIcon) { 570 imageOffset = 0; 571 } 572 else if (imageOffset == -1) { 573 imageOffset = getLabelStart(); 574 } 575 if(getComponentOrientation().isLeftToRight()) { 576 paintFocus(g, imageOffset, 0, getWidth() - imageOffset, 577 getHeight(), bColor); 578 } else { 579 paintFocus(g, 0, 0, getWidth() - imageOffset, getHeight(), bColor); 580 } 581 } 582 super.paint(g); 583 } 584 585 private void paintFocus(Graphics g, int x, int y, int w, int h, Color notColor) { 586 Color bsColor = getBorderSelectionColor(); 587 588 if (bsColor != null && (selected || !drawDashedFocusIndicator)) { 589 g.setColor(bsColor); 590 g.drawRect(x, y, w - 1, h - 1); 591 } 592 if (drawDashedFocusIndicator && notColor != null) { 593 if (treeBGColor != notColor) { 594 treeBGColor = notColor; 595 focusBGColor = new Color(~notColor.getRGB()); 596 } 597 g.setColor(focusBGColor); 598 BasicGraphicsUtils.drawDashedRect(g, x, y, w, h); 599 } 600 } 601 602 private int getLabelStart() { 603 Icon currentI = getIcon(); 604 if(currentI != null && getText() != null) { 605 return currentI.getIconWidth() + Math.max(0, getIconTextGap() - 1); 606 } 607 return 0; 608 } 609 610 /** 611 * Overrides <code>JComponent.getPreferredSize</code> to 612 * return slightly wider preferred size value. 613 */ 614 public Dimension getPreferredSize() { 615 Dimension retDimension = super.getPreferredSize(); 616 617 if(retDimension != null) 618 retDimension = new Dimension(retDimension.width + 3, 619 retDimension.height); 620 return retDimension; 621 } 622 623 /** 624 * Overridden for performance reasons. 625 * See the <a href="#override">Implementation Note</a> 626 * for more information. 627 */ 628 public void validate() {} 629 630 /** 631 * Overridden for performance reasons. 632 * See the <a href="#override">Implementation Note</a> 633 * for more information. 634 * 635 * @since 1.5 636 */ 637 public void invalidate() {} 638 639 /** 640 * Overridden for performance reasons. 641 * See the <a href="#override">Implementation Note</a> 642 * for more information. 643 */ 644 public void revalidate() {} 645 646 /** 647 * Overridden for performance reasons. 648 * See the <a href="#override">Implementation Note</a> 649 * for more information. 650 */ 651 public void repaint(long tm, int x, int y, int width, int height) {} 652 653 /** 654 * Overridden for performance reasons. 655 * See the <a href="#override">Implementation Note</a> 656 * for more information. 657 */ 658 public void repaint(Rectangle r) {} 659 660 /** 661 * Overridden for performance reasons. 662 * See the <a href="#override">Implementation Note</a> 663 * for more information. 664 * 665 * @since 1.5 666 */ 667 public void repaint() {} 668 669 /** 670 * Overridden for performance reasons. 671 * See the <a href="#override">Implementation Note</a> 672 * for more information. 673 */ 674 protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { 675 // Strings get interned... 676 if (propertyName == "text" 677 || ((propertyName == "font" || propertyName == "foreground") 678 && oldValue != newValue 679 && getClientProperty(javax.swing.plaf.basic.BasicHTML.propertyKey) != null)) { 680 681 super.firePropertyChange(propertyName, oldValue, newValue); 682 } 683 } 684 685 /** 686 * Overridden for performance reasons. 687 * See the <a href="#override">Implementation Note</a> 688 * for more information. 689 */ 690 public void firePropertyChange(String propertyName, byte oldValue, byte newValue) {} 691 692 /** 693 * Overridden for performance reasons. 694 * See the <a href="#override">Implementation Note</a> 695 * for more information. 696 */ 697 public void firePropertyChange(String propertyName, char oldValue, char newValue) {} 698 699 /** 700 * Overridden for performance reasons. 701 * See the <a href="#override">Implementation Note</a> 702 * for more information. 703 */ 704 public void firePropertyChange(String propertyName, short oldValue, short newValue) {} 705 706 /** 707 * Overridden for performance reasons. 708 * See the <a href="#override">Implementation Note</a> 709 * for more information. 710 */ 711 public void firePropertyChange(String propertyName, int oldValue, int newValue) {} 712 713 /** 714 * Overridden for performance reasons. 715 * See the <a href="#override">Implementation Note</a> 716 * for more information. 717 */ 718 public void firePropertyChange(String propertyName, long oldValue, long newValue) {} 719 720 /** 721 * Overridden for performance reasons. 722 * See the <a href="#override">Implementation Note</a> 723 * for more information. 724 */ 725 public void firePropertyChange(String propertyName, float oldValue, float newValue) {} 726 727 /** 728 * Overridden for performance reasons. 729 * See the <a href="#override">Implementation Note</a> 730 * for more information. 731 */ 732 public void firePropertyChange(String propertyName, double oldValue, double newValue) {} 733 734 /** 735 * Overridden for performance reasons. 736 * See the <a href="#override">Implementation Note</a> 737 * for more information. 738 */ 739 public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {} 740 741} 742