1 /* 2 * Copyright (c) 1997, 2014, 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" align="left"> 65 * <th style="background-color:#CCCCFF" align="left">Property: 66 * <th style="background-color:#CCCCFF" 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 name="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 transient protected Icon closedIcon; 135 136 /** Icon used to show leaf nodes. */ 137 transient protected Icon leafIcon; 138 139 /** Icon used to show non-leaf nodes that are expanded. */ 140 transient protected 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 UIManager)) { 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 public Icon getDefaultOpenIcon() { 239 return DefaultLookup.getIcon(this, ui, "Tree.openIcon"); 240 } 241 242 /** 243 * Returns the default icon, for the current laf, that is used to 244 * represent non-leaf nodes that are not expanded. 245 */ 246 public Icon getDefaultClosedIcon() { 247 return DefaultLookup.getIcon(this, ui, "Tree.closedIcon"); 248 } 249 250 /** 251 * Returns the default icon, for the current laf, that is used to 252 * represent leaf nodes. 253 */ 254 public Icon getDefaultLeafIcon() { 255 return DefaultLookup.getIcon(this, ui, "Tree.leafIcon"); 256 } 257 258 /** 259 * Sets the icon used to represent non-leaf nodes that are expanded. 260 */ 261 public void setOpenIcon(Icon newIcon) { 262 openIcon = newIcon; 263 } 264 265 /** 266 * Returns the icon used to represent non-leaf nodes that are expanded. 267 */ 268 public Icon getOpenIcon() { 269 return openIcon; 270 } 271 272 /** 273 * Sets the icon used to represent non-leaf nodes that are not expanded. 274 */ 275 public void setClosedIcon(Icon newIcon) { 276 closedIcon = newIcon; 277 } 278 279 /** 280 * Returns the icon used to represent non-leaf nodes that are not 281 * expanded. 282 */ 283 public Icon getClosedIcon() { 284 return closedIcon; 285 } 286 287 /** 288 * Sets the icon used to represent leaf nodes. 289 */ 290 public void setLeafIcon(Icon newIcon) { 291 leafIcon = newIcon; 292 } 293 294 /** 295 * Returns the icon used to represent leaf nodes. 296 */ 297 public Icon getLeafIcon() { 298 return leafIcon; 299 } 300 301 /** 302 * Sets the color the text is drawn with when the node is selected. 303 */ 304 public void setTextSelectionColor(Color newColor) { 305 textSelectionColor = newColor; 306 } 307 308 /** 309 * Returns the color the text is drawn with when the node is selected. 310 */ 311 public Color getTextSelectionColor() { 312 return textSelectionColor; 313 } 314 315 /** 316 * Sets the color the text is drawn with when the node isn't selected. 317 */ 318 public void setTextNonSelectionColor(Color newColor) { 319 textNonSelectionColor = newColor; 320 } 321 322 /** 323 * Returns the color the text is drawn with when the node isn't selected. 324 */ 325 public Color getTextNonSelectionColor() { 326 return textNonSelectionColor; 327 } 328 329 /** 330 * Sets the color to use for the background if node is selected. 331 */ 332 public void setBackgroundSelectionColor(Color newColor) { 333 backgroundSelectionColor = newColor; 334 } 335 336 337 /** 338 * Returns the color to use for the background if node is selected. 339 */ 340 public Color getBackgroundSelectionColor() { 341 return backgroundSelectionColor; 342 } 343 344 /** 345 * Sets the background color to be used for non selected nodes. 346 */ 347 public void setBackgroundNonSelectionColor(Color newColor) { 348 backgroundNonSelectionColor = newColor; 349 } 350 351 /** 352 * Returns the background color to be used for non selected nodes. 353 */ 354 public Color getBackgroundNonSelectionColor() { 355 return backgroundNonSelectionColor; 356 } 357 358 /** 359 * Sets the color to use for the border. 360 */ 361 public void setBorderSelectionColor(Color newColor) { 362 borderSelectionColor = newColor; 363 } 364 365 /** 366 * Returns the color the border is drawn. 367 */ 368 public Color getBorderSelectionColor() { 369 return borderSelectionColor; 370 } 371 372 /** 373 * Subclassed to map <code>FontUIResource</code>s to null. If 374 * <code>font</code> is null, or a <code>FontUIResource</code>, this 375 * has the effect of letting the font of the JTree show 376 * through. On the other hand, if <code>font</code> is non-null, and not 377 * a <code>FontUIResource</code>, the font becomes <code>font</code>. 378 */ 379 public void setFont(Font font) { 380 if(font instanceof FontUIResource) 381 font = null; 382 super.setFont(font); 383 } 384 385 /** 386 * Gets the font of this component. 387 * @return this component's font; if a font has not been set 388 * for this component, the font of its parent is returned 389 */ 390 public Font getFont() { 391 Font font = super.getFont(); 392 393 if (font == null && tree != null) { 394 // Strive to return a non-null value, otherwise the html support 395 // will typically pick up the wrong font in certain situations. 396 font = tree.getFont(); 397 } 398 return font; 399 } 400 401 /** 402 * Subclassed to map <code>ColorUIResource</code>s to null. If 403 * <code>color</code> is null, or a <code>ColorUIResource</code>, this 404 * has the effect of letting the background color of the JTree show 405 * through. On the other hand, if <code>color</code> is non-null, and not 406 * a <code>ColorUIResource</code>, the background becomes 407 * <code>color</code>. 408 */ 409 public void setBackground(Color color) { 410 if(color instanceof ColorUIResource) 411 color = null; 412 super.setBackground(color); 413 } 414 415 /** 416 * Configures the renderer based on the passed in components. 417 * The value is set from messaging the tree with 418 * <code>convertValueToText</code>, which ultimately invokes 419 * <code>toString</code> on <code>value</code>. 420 * The foreground color is set based on the selection and the icon 421 * is set based on the <code>leaf</code> and <code>expanded</code> 422 * parameters. 423 */ 424 public Component getTreeCellRendererComponent(JTree tree, Object value, 425 boolean sel, 426 boolean expanded, 427 boolean leaf, int row, 428 boolean hasFocus) { 429 String stringValue = tree.convertValueToText(value, sel, 430 expanded, leaf, row, hasFocus); 431 432 this.tree = tree; 433 this.hasFocus = hasFocus; 434 setText(stringValue); 435 436 Color fg = null; 437 isDropCell = false; 438 439 JTree.DropLocation dropLocation = tree.getDropLocation(); 440 if (dropLocation != null 441 && dropLocation.getChildIndex() == -1 442 && tree.getRowForPath(dropLocation.getPath()) == row) { 443 444 Color col = DefaultLookup.getColor(this, ui, "Tree.dropCellForeground"); 445 if (col != null) { 446 fg = col; 447 } else { 448 fg = getTextSelectionColor(); 449 } 450 451 isDropCell = true; 452 } else if (sel) { 453 fg = getTextSelectionColor(); 454 } else { 455 fg = getTextNonSelectionColor(); 456 } 457 458 setForeground(fg); 459 460 Icon icon = null; 461 if (leaf) { 462 icon = getLeafIcon(); 463 } else if (expanded) { 464 icon = getOpenIcon(); 465 } else { 466 icon = getClosedIcon(); 467 } 468 469 if (!tree.isEnabled()) { 470 setEnabled(false); 471 LookAndFeel laf = UIManager.getLookAndFeel(); 472 Icon disabledIcon = laf.getDisabledIcon(tree, icon); 473 if (disabledIcon != null) icon = disabledIcon; 474 setDisabledIcon(icon); 475 } else { 476 setEnabled(true); 477 setIcon(icon); 478 } 479 setComponentOrientation(tree.getComponentOrientation()); 480 481 selected = sel; 482 483 return this; 484 } 485 486 /** 487 * Paints the value. The background is filled based on selected. 488 */ 489 public void paint(Graphics g) { 490 Color bColor; 491 492 if (isDropCell) { 493 bColor = DefaultLookup.getColor(this, ui, "Tree.dropCellBackground"); 494 if (bColor == null) { 495 bColor = getBackgroundSelectionColor(); 496 } 497 } else if (selected) { 498 bColor = getBackgroundSelectionColor(); 499 } else { 500 bColor = getBackgroundNonSelectionColor(); 501 if (bColor == null) { 502 bColor = getBackground(); 503 } 504 } 505 506 int imageOffset = -1; 507 if (bColor != null && fillBackground) { 508 imageOffset = getLabelStart(); 509 g.setColor(bColor); 510 if(getComponentOrientation().isLeftToRight()) { 511 g.fillRect(imageOffset, 0, getWidth() - imageOffset, 512 getHeight()); 513 } else { 514 g.fillRect(0, 0, getWidth() - imageOffset, 515 getHeight()); 516 } 517 } 518 519 if (hasFocus) { 520 if (drawsFocusBorderAroundIcon) { 521 imageOffset = 0; 522 } 523 else if (imageOffset == -1) { 524 imageOffset = getLabelStart(); 525 } 526 if(getComponentOrientation().isLeftToRight()) { 527 paintFocus(g, imageOffset, 0, getWidth() - imageOffset, 528 getHeight(), bColor); 529 } else { 530 paintFocus(g, 0, 0, getWidth() - imageOffset, getHeight(), bColor); 531 } 532 } 533 super.paint(g); 534 } 535 536 private void paintFocus(Graphics g, int x, int y, int w, int h, Color notColor) { 537 Color bsColor = getBorderSelectionColor(); 538 539 if (bsColor != null && (selected || !drawDashedFocusIndicator)) { 540 g.setColor(bsColor); 541 g.drawRect(x, y, w - 1, h - 1); 542 } 543 if (drawDashedFocusIndicator && notColor != null) { 544 if (treeBGColor != notColor) { 545 treeBGColor = notColor; 546 focusBGColor = new Color(~notColor.getRGB()); 547 } 548 g.setColor(focusBGColor); 549 BasicGraphicsUtils.drawDashedRect(g, x, y, w, h); 550 } 551 } 552 553 private int getLabelStart() { 554 Icon currentI = getIcon(); 555 if(currentI != null && getText() != null) { 556 return currentI.getIconWidth() + Math.max(0, getIconTextGap() - 1); 557 } 558 return 0; 559 } 560 561 /** 562 * Overrides <code>JComponent.getPreferredSize</code> to 563 * return slightly wider preferred size value. 564 */ 565 public Dimension getPreferredSize() { 566 Dimension retDimension = super.getPreferredSize(); 567 568 if(retDimension != null) 569 retDimension = new Dimension(retDimension.width + 3, 570 retDimension.height); 571 return retDimension; 572 } 573 574 /** 575 * Overridden for performance reasons. 576 * See the <a href="#override">Implementation Note</a> 577 * for more information. 578 */ 579 public void validate() {} 580 581 /** 582 * Overridden for performance reasons. 583 * See the <a href="#override">Implementation Note</a> 584 * for more information. 585 * 586 * @since 1.5 587 */ 588 public void invalidate() {} 589 590 /** 591 * Overridden for performance reasons. 592 * See the <a href="#override">Implementation Note</a> 593 * for more information. 594 */ 595 public void revalidate() {} 596 597 /** 598 * Overridden for performance reasons. 599 * See the <a href="#override">Implementation Note</a> 600 * for more information. 601 */ 602 public void repaint(long tm, int x, int y, int width, int height) {} 603 604 /** 605 * Overridden for performance reasons. 606 * See the <a href="#override">Implementation Note</a> 607 * for more information. 608 */ 609 public void repaint(Rectangle r) {} 610 611 /** 612 * Overridden for performance reasons. 613 * See the <a href="#override">Implementation Note</a> 614 * for more information. 615 * 616 * @since 1.5 617 */ 618 public void repaint() {} 619 620 /** 621 * Overridden for performance reasons. 622 * See the <a href="#override">Implementation Note</a> 623 * for more information. 624 */ 625 protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { 626 // Strings get interned... 627 if (propertyName == "text" 628 || ((propertyName == "font" || propertyName == "foreground") 629 && oldValue != newValue 630 && getClientProperty(javax.swing.plaf.basic.BasicHTML.propertyKey) != null)) { 631 632 super.firePropertyChange(propertyName, oldValue, newValue); 633 } 634 } 635 636 /** 637 * Overridden for performance reasons. 638 * See the <a href="#override">Implementation Note</a> 639 * for more information. 640 */ 641 public void firePropertyChange(String propertyName, byte oldValue, byte newValue) {} 642 643 /** 644 * Overridden for performance reasons. 645 * See the <a href="#override">Implementation Note</a> 646 * for more information. 647 */ 648 public void firePropertyChange(String propertyName, char oldValue, char newValue) {} 649 650 /** 651 * Overridden for performance reasons. 652 * See the <a href="#override">Implementation Note</a> 653 * for more information. 654 */ 655 public void firePropertyChange(String propertyName, short oldValue, short newValue) {} 656 657 /** 658 * Overridden for performance reasons. 659 * See the <a href="#override">Implementation Note</a> 660 * for more information. 661 */ 662 public void firePropertyChange(String propertyName, int oldValue, int newValue) {} 663 664 /** 665 * Overridden for performance reasons. 666 * See the <a href="#override">Implementation Note</a> 667 * for more information. 668 */ 669 public void firePropertyChange(String propertyName, long oldValue, long newValue) {} 670 671 /** 672 * Overridden for performance reasons. 673 * See the <a href="#override">Implementation Note</a> 674 * for more information. 675 */ 676 public void firePropertyChange(String propertyName, float oldValue, float newValue) {} 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, double oldValue, double 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, boolean oldValue, boolean newValue) {} 691 692 }