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