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