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 }