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&trade;
  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 }