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