1/*
2 * Copyright (c) 2002, 2016, 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 */
25package sun.swing.plaf.synth;
26
27import javax.swing.plaf.synth.*;
28import java.awt.*;
29import java.util.*;
30import javax.swing.*;
31import javax.swing.plaf.*;
32
33/**
34 * Default implementation of SynthStyle. Has setters for the various
35 * SynthStyle methods. Many of the properties can be specified for all states,
36 * using SynthStyle directly, or a specific state using one of the StateInfo
37 * methods.
38 * <p>
39 * Beyond the constructor a subclass should override the <code>addTo</code>
40 * and <code>clone</code> methods, these are used when the Styles are being
41 * merged into a resulting style.
42 *
43 * @author Scott Violet
44 */
45public class DefaultSynthStyle extends SynthStyle implements Cloneable {
46
47    private static final Object PENDING = new Object();
48
49    /**
50     * Should the component be opaque?
51     */
52    private boolean opaque;
53    /**
54     * Insets.
55     */
56    private Insets insets;
57    /**
58     * Information specific to ComponentState.
59     */
60    private StateInfo[] states;
61    /**
62     * User specific data.
63     */
64    private Map<Object, Object> data;
65
66    /**
67     * Font to use if there is no matching StateInfo, or the StateInfo doesn't
68     * define one.
69     */
70    private Font font;
71
72    /**
73     * SynthGraphics, may be null.
74     */
75    private SynthGraphicsUtils synthGraphics;
76
77    /**
78     * Painter to use if the StateInfo doesn't have one.
79     */
80    private SynthPainter painter;
81
82
83    /**
84     * Nullary constructor, intended for subclassers.
85     */
86    public DefaultSynthStyle() {
87    }
88
89    /**
90     * Creates a new DefaultSynthStyle that is a copy of the passed in
91     * style. Any StateInfo's of the passed in style are clonsed as well.
92     *
93     * @param style Style to duplicate
94     */
95    public DefaultSynthStyle(DefaultSynthStyle style) {
96        opaque = style.opaque;
97        if (style.insets != null) {
98            insets = new Insets(style.insets.top, style.insets.left,
99                                style.insets.bottom, style.insets.right);
100        }
101        if (style.states != null) {
102            states = new StateInfo[style.states.length];
103            for (int counter = style.states.length - 1; counter >= 0;
104                     counter--) {
105                states[counter] = (StateInfo)style.states[counter].clone();
106            }
107        }
108        if (style.data != null) {
109            data = new HashMap<>();
110            data.putAll(style.data);
111        }
112        font = style.font;
113        synthGraphics = style.synthGraphics;
114        painter = style.painter;
115    }
116
117    /**
118     * Creates a new DefaultSynthStyle.
119     *
120     * @param insets Insets for the Style
121     * @param opaque Whether or not the background is completely painted in
122     *        an opaque color
123     * @param states StateInfos describing properties per state
124     * @param data Style specific data.
125     */
126    public DefaultSynthStyle(Insets insets, boolean opaque,
127                             StateInfo[] states, Map<Object, Object> data) {
128        this.insets = insets;
129        this.opaque = opaque;
130        this.states = states;
131        this.data = data;
132    }
133
134    public Color getColor(SynthContext context, ColorType type) {
135        return getColor(context.getComponent(), context.getRegion(),
136                        context.getComponentState(), type);
137    }
138
139    public Color getColor(JComponent c, Region id, int state,
140                          ColorType type) {
141        // For the enabled state, prefer the widget's colors
142        if (!id.isSubregion() && state == SynthConstants.ENABLED) {
143            if (type == ColorType.BACKGROUND) {
144                return c.getBackground();
145            }
146            else if (type == ColorType.FOREGROUND) {
147                return c.getForeground();
148            }
149            else if (type == ColorType.TEXT_FOREGROUND) {
150                // If getForeground returns a non-UIResource it means the
151                // developer has explicitly set the foreground, use it over
152                // that of TEXT_FOREGROUND as that is typically the expected
153                // behavior.
154                Color color = c.getForeground();
155                if (!(color instanceof UIResource)) {
156                    return color;
157                }
158            }
159        }
160        // Then use what we've locally defined
161        Color color = getColorForState(c, id, state, type);
162        if (color == null) {
163            // No color, fallback to that of the widget.
164            if (type == ColorType.BACKGROUND ||
165                        type == ColorType.TEXT_BACKGROUND) {
166                return c.getBackground();
167            }
168            else if (type == ColorType.FOREGROUND ||
169                     type == ColorType.TEXT_FOREGROUND) {
170                return c.getForeground();
171            }
172        }
173        return color;
174    }
175
176    protected Color getColorForState(SynthContext context, ColorType type) {
177        return getColorForState(context.getComponent(), context.getRegion(),
178                                context.getComponentState(), type);
179    }
180
181    /**
182     * Returns the color for the specified state.
183     *
184     * @param c JComponent the style is associated with
185     * @param id Region identifier
186     * @param state State of the region.
187     * @param type Type of color being requested.
188     * @return Color to render with
189     */
190    protected Color getColorForState(JComponent c, Region id, int state,
191                                     ColorType type) {
192        // Use the best state.
193        StateInfo si = getStateInfo(state);
194        Color color;
195        if (si != null && (color = si.getColor(type)) != null) {
196            return color;
197        }
198        if (si == null || si.getComponentState() != 0) {
199            si = getStateInfo(0);
200            if (si != null) {
201                return si.getColor(type);
202            }
203        }
204        return null;
205    }
206
207    /**
208     * Sets the font that is used if there is no matching StateInfo, or
209     * it does not define a font.
210     *
211     * @param font Font to use for rendering
212     */
213    public void setFont(Font font) {
214        this.font = font;
215    }
216
217    public Font getFont(SynthContext state) {
218        return getFont(state.getComponent(), state.getRegion(),
219                       state.getComponentState());
220    }
221
222    public Font getFont(JComponent c, Region id, int state) {
223        if (!id.isSubregion() && state == SynthConstants.ENABLED) {
224            return c.getFont();
225        }
226        Font cFont = c.getFont();
227        if (cFont != null && !(cFont instanceof UIResource)) {
228            return cFont;
229        }
230        return getFontForState(c, id, state);
231    }
232
233    /**
234     * Returns the font for the specified state. This should NOT callback
235     * to the JComponent.
236     *
237     * @param c JComponent the style is associated with
238     * @param id Region identifier
239     * @param state State of the region.
240     * @return Font to render with
241     */
242    protected Font getFontForState(JComponent c, Region id, int state) {
243        if (c == null) {
244            return this.font;
245        }
246        // First pass, look for the best match
247        StateInfo si = getStateInfo(state);
248        Font font;
249        if (si != null && (font = si.getFont()) != null) {
250            return font;
251        }
252        if (si == null || si.getComponentState() != 0) {
253            si = getStateInfo(0);
254            if (si != null && (font = si.getFont()) != null) {
255                return font;
256            }
257        }
258        // Fallback font.
259        return this.font;
260    }
261
262    protected Font getFontForState(SynthContext context) {
263        return getFontForState(context.getComponent(), context.getRegion(),
264                               context.getComponentState());
265    }
266
267    /**
268     * Sets the SynthGraphicsUtils that will be used for rendering.
269     *
270     * @param graphics SynthGraphics
271     */
272    public void setGraphicsUtils(SynthGraphicsUtils graphics) {
273        this.synthGraphics = graphics;
274    }
275
276    /**
277     * Returns a SynthGraphicsUtils.
278     *
279     * @param context SynthContext identifying requestor
280     * @return SynthGraphicsUtils
281     */
282    public SynthGraphicsUtils getGraphicsUtils(SynthContext context) {
283        if (synthGraphics == null) {
284            return super.getGraphicsUtils(context);
285        }
286        return synthGraphics;
287    }
288
289    /**
290     * Sets the insets.
291     *
292     * @param insets the new insets.
293     */
294    public void setInsets(Insets insets) {
295        this.insets = insets;
296    }
297
298    /**
299     * Returns the Insets. If <code>to</code> is non-null the resulting
300     * insets will be placed in it, otherwise a new Insets object will be
301     * created and returned.
302     *
303     * @param state SynthContext identifying requestor
304     * @param to Where to place Insets
305     * @return Insets.
306     */
307    public Insets getInsets(SynthContext state, Insets to) {
308        if (to == null) {
309            to = new Insets(0, 0, 0, 0);
310        }
311        if (insets != null) {
312            to.left = insets.left;
313            to.right = insets.right;
314            to.top = insets.top;
315            to.bottom = insets.bottom;
316        }
317        else {
318            to.left = to.right = to.top = to.bottom = 0;
319        }
320        return to;
321    }
322
323    /**
324     * Sets the Painter to use for the border.
325     *
326     * @param painter Painter for the Border.
327     */
328    public void setPainter(SynthPainter painter) {
329        this.painter = painter;
330    }
331
332    /**
333     * Returns the Painter for the passed in Component. This may return null.
334     *
335     * @param ss SynthContext identifying requestor
336     * @return Painter for the border
337     */
338    public SynthPainter getPainter(SynthContext ss) {
339        return painter;
340    }
341
342    /**
343     * Sets whether or not the JComponent should be opaque.
344     *
345     * @param opaque Whether or not the JComponent should be opaque.
346     */
347    public void setOpaque(boolean opaque) {
348        this.opaque = opaque;
349    }
350
351    /**
352     * Returns the value to initialize the opacity property of the Component
353     * to. A Style should NOT assume the opacity will remain this value, the
354     * developer may reset it or override it.
355     *
356     * @param ss SynthContext identifying requestor
357     * @return opaque Whether or not the JComponent is opaque.
358     */
359    public boolean isOpaque(SynthContext ss) {
360        return opaque;
361    }
362
363    /**
364     * Sets style specific values. This does NOT copy the data, it
365     * assigns it directly to this Style.
366     *
367     * @param data Style specific values
368     */
369    public void setData(Map<Object, Object> data) {
370        this.data = data;
371    }
372
373    /**
374     * Returns the style specific data.
375     *
376     * @return Style specific data.
377     */
378    public Map<Object, Object> getData() {
379        return data;
380    }
381
382    /**
383     * Getter for a region specific style property.
384     *
385     * @param state SynthContext identifying requestor
386     * @param key Property being requested.
387     * @return Value of the named property
388     */
389    public Object get(SynthContext state, Object key) {
390        // Look for the best match
391        StateInfo si = getStateInfo(state.getComponentState());
392        if (si != null && si.getData() != null && getKeyFromData(si.getData(), key) != null) {
393            return getKeyFromData(si.getData(), key);
394        }
395        si = getStateInfo(0);
396        if (si != null && si.getData() != null && getKeyFromData(si.getData(), key) != null) {
397            return getKeyFromData(si.getData(), key);
398        }
399        if(getKeyFromData(data, key) != null)
400          return getKeyFromData(data, key);
401        return getDefaultValue(state, key);
402    }
403
404
405    private Object getKeyFromData(Map<Object, Object> stateData, Object key) {
406          Object value = null;
407          if (stateData != null) {
408
409            synchronized(stateData) {
410                value = stateData.get(key);
411            }
412            while (value == PENDING) {
413                synchronized(stateData) {
414                    try {
415                        stateData.wait();
416                    } catch (InterruptedException ie) {}
417                    value = stateData.get(key);
418                }
419            }
420            if (value instanceof UIDefaults.LazyValue) {
421                synchronized(stateData) {
422                    stateData.put(key, PENDING);
423                }
424                value = ((UIDefaults.LazyValue)value).createValue(null);
425                synchronized(stateData) {
426                    stateData.put(key, value);
427                    stateData.notifyAll();
428                }
429            }
430        }
431        return value;
432    }
433
434    /**
435     * Returns the default value for a particular property.  This is only
436     * invoked if this style doesn't define a property for <code>key</code>.
437     *
438     * @param context SynthContext identifying requestor
439     * @param key Property being requested.
440     * @return Value of the named property
441     */
442    public Object getDefaultValue(SynthContext context, Object key) {
443        return super.get(context, key);
444    }
445
446    /**
447     * Creates a clone of this style.
448     *
449     * @return Clone of this style
450     */
451    public Object clone() {
452        DefaultSynthStyle style;
453        try {
454            style = (DefaultSynthStyle)super.clone();
455        } catch (CloneNotSupportedException cnse) {
456            return null;
457        }
458        if (states != null) {
459            style.states = new StateInfo[states.length];
460            for (int counter = states.length - 1; counter >= 0; counter--) {
461                style.states[counter] = (StateInfo)states[counter].clone();
462            }
463        }
464        if (data != null) {
465            style.data = new HashMap<>();
466            style.data.putAll(data);
467        }
468        return style;
469    }
470
471    /**
472     * Merges the contents of this Style with that of the passed in Style,
473     * returning the resulting merged syle. Properties of this
474     * <code>DefaultSynthStyle</code> will take precedence over those of the
475     * passed in <code>DefaultSynthStyle</code>. For example, if this
476     * style specifics a non-null font, the returned style will have its
477     * font so to that regardless of the <code>style</code>'s font.
478     *
479     * @param style Style to add our styles to
480     * @return Merged style.
481     */
482    public DefaultSynthStyle addTo(DefaultSynthStyle style) {
483        if (insets != null) {
484            style.insets = this.insets;
485        }
486        if (font != null) {
487            style.font = this.font;
488        }
489        if (painter != null) {
490            style.painter = this.painter;
491        }
492        if (synthGraphics != null) {
493            style.synthGraphics = this.synthGraphics;
494        }
495        style.opaque = opaque;
496        if (states != null) {
497            if (style.states == null) {
498                style.states = new StateInfo[states.length];
499                for (int counter = states.length - 1; counter >= 0; counter--){
500                    if (states[counter] != null) {
501                        style.states[counter] = (StateInfo)states[counter].
502                                                clone();
503                    }
504                }
505            }
506            else {
507                // Find the number of new states in unique, merging any
508                // matching states as we go. Also, move any merge styles
509                // to the end to give them precedence.
510                int unique = 0;
511                // Number of StateInfos that match.
512                int matchCount = 0;
513                int maxOStyles = style.states.length;
514                for (int thisCounter = states.length - 1; thisCounter >= 0;
515                         thisCounter--) {
516                    int state = states[thisCounter].getComponentState();
517                    boolean found = false;
518
519                    for (int oCounter = maxOStyles - 1 - matchCount;
520                             oCounter >= 0; oCounter--) {
521                        if (state == style.states[oCounter].
522                                           getComponentState()) {
523                            style.states[oCounter] = states[thisCounter].
524                                        addTo(style.states[oCounter]);
525                            // Move StateInfo to end, giving it precedence.
526                            StateInfo tmp = style.states[maxOStyles - 1 -
527                                                         matchCount];
528                            style.states[maxOStyles - 1 - matchCount] =
529                                  style.states[oCounter];
530                            style.states[oCounter] = tmp;
531                            matchCount++;
532                            found = true;
533                            break;
534                        }
535                    }
536                    if (!found) {
537                        unique++;
538                    }
539                }
540                if (unique != 0) {
541                    // There are states that exist in this Style that
542                    // don't exist in the other style, recreate the array
543                    // and add them.
544                    StateInfo[] newStates = new StateInfo[
545                                   unique + maxOStyles];
546                    int newIndex = maxOStyles;
547
548                    System.arraycopy(style.states, 0, newStates, 0,maxOStyles);
549                    for (int thisCounter = states.length - 1; thisCounter >= 0;
550                             thisCounter--) {
551                        int state = states[thisCounter].getComponentState();
552                        boolean found = false;
553
554                        for (int oCounter = maxOStyles - 1; oCounter >= 0;
555                                 oCounter--) {
556                            if (state == style.states[oCounter].
557                                               getComponentState()) {
558                                found = true;
559                                break;
560                            }
561                        }
562                        if (!found) {
563                            newStates[newIndex++] = (StateInfo)states[
564                                      thisCounter].clone();
565                        }
566                    }
567                    style.states = newStates;
568                }
569            }
570        }
571        if (data != null) {
572            if (style.data == null) {
573                style.data = new HashMap<>();
574            }
575            style.data.putAll(data);
576        }
577        return style;
578    }
579
580    /**
581     * Sets the array of StateInfo's which are used to specify properties
582     * specific to a particular style.
583     *
584     * @param states StateInfos
585     */
586    public void setStateInfo(StateInfo[] states) {
587        this.states = states;
588    }
589
590    /**
591     * Returns the array of StateInfo's that that are used to specify
592     * properties specific to a particular style.
593     *
594     * @return Array of StateInfos.
595     */
596    public StateInfo[] getStateInfo() {
597        return states;
598    }
599
600    /**
601     * Returns the best matching StateInfo for a particular state.
602     *
603     * @param state Component state.
604     * @return Best matching StateInfo, or null
605     */
606    public StateInfo getStateInfo(int state) {
607        // Use the StateInfo with the most bits that matches that of state.
608        // If there is none, than fallback to
609        // the StateInfo with a state of 0, indicating it'll match anything.
610
611        // Consider if we have 3 StateInfos a, b and c with states:
612        // SELECTED, SELECTED | ENABLED, 0
613        //
614        // Input                          Return Value
615        // -----                          ------------
616        // SELECTED                       a
617        // SELECTED | ENABLED             b
618        // MOUSE_OVER                     c
619        // SELECTED | ENABLED | FOCUSED   b
620        // ENABLED                        c
621
622        if (states != null) {
623            int bestCount = 0;
624            int bestIndex = -1;
625            int wildIndex = -1;
626
627            if (state == 0) {
628                for (int counter = states.length - 1; counter >= 0;counter--) {
629                    if (states[counter].getComponentState() == 0) {
630                        return states[counter];
631                    }
632                }
633                return null;
634            }
635            for (int counter = states.length - 1; counter >= 0; counter--) {
636                int oState = states[counter].getComponentState();
637
638                if (oState == 0) {
639                    if (wildIndex == -1) {
640                        wildIndex = counter;
641                    }
642                }
643                else if ((state & oState) == oState) {
644                    // This is key, we need to make sure all bits of the
645                    // StateInfo match, otherwise a StateInfo with
646                    // SELECTED | ENABLED would match ENABLED, which we
647                    // don't want.
648                    int bitCount = Integer.bitCount(oState);
649                    if (bitCount > bestCount) {
650                        bestIndex = counter;
651                        bestCount = bitCount;
652                    }
653                }
654            }
655            if (bestIndex != -1) {
656                return states[bestIndex];
657            }
658            if (wildIndex != -1) {
659                return states[wildIndex];
660            }
661          }
662          return null;
663    }
664
665
666    public String toString() {
667        StringBuilder sb = new StringBuilder();
668
669        sb.append(super.toString()).append(',');
670
671        sb.append("data=").append(data).append(',');
672
673        sb.append("font=").append(font).append(',');
674
675        sb.append("insets=").append(insets).append(',');
676
677        sb.append("synthGraphics=").append(synthGraphics).append(',');
678
679        sb.append("painter=").append(painter).append(',');
680
681        StateInfo[] states = getStateInfo();
682        if (states != null) {
683            sb.append("states[");
684            for (StateInfo state : states) {
685                sb.append(state.toString()).append(',');
686            }
687            sb.append(']').append(',');
688        }
689
690        // remove last newline
691        sb.deleteCharAt(sb.length() - 1);
692
693        return sb.toString();
694    }
695
696
697    /**
698     * StateInfo represents Style information specific to the state of
699     * a component.
700     */
701    public static class StateInfo {
702        private Map<Object, Object> data;
703        private Font font;
704        private Color[] colors;
705        private int state;
706
707        /**
708         * Creates a new StateInfo.
709         */
710        public StateInfo() {
711        }
712
713        /**
714         * Creates a new StateInfo with the specified properties
715         *
716         * @param state Component state(s) that this StateInfo should be used
717         * for
718         * @param font Font for this state
719         * @param colors Colors for this state
720         */
721        public StateInfo(int state, Font font, Color[] colors) {
722            this.state = state;
723            this.font = font;
724            this.colors = colors;
725        }
726
727        /**
728         * Creates a new StateInfo that is a copy of the passed in
729         * StateInfo.
730         *
731         * @param info StateInfo to copy.
732         */
733        public StateInfo(StateInfo info) {
734            this.state = info.state;
735            this.font = info.font;
736            if(info.data != null) {
737               if(data == null) {
738                  data = new HashMap<>();
739               }
740               data.putAll(info.data);
741            }
742            if (info.colors != null) {
743                this.colors = new Color[info.colors.length];
744                System.arraycopy(info.colors, 0, colors, 0,info.colors.length);
745            }
746        }
747
748        public Map<Object, Object> getData() {
749            return data;
750        }
751
752        public void setData(Map<Object, Object> data) {
753            this.data = data;
754        }
755
756        /**
757         * Sets the font for this state.
758         *
759         * @param font Font to use for rendering
760         */
761        public void setFont(Font font) {
762            this.font = font;
763        }
764
765        /**
766         * Returns the font for this state.
767         *
768         * @return Returns the font to use for rendering this state
769         */
770        public Font getFont() {
771            return font;
772        }
773
774        /**
775         * Sets the array of colors to use for rendering this state. This
776         * is indexed by <code>ColorType.getID()</code>.
777         *
778         * @param colors Array of colors
779         */
780        public void setColors(Color[] colors) {
781            this.colors = colors;
782        }
783
784        /**
785         * Returns the array of colors to use for rendering this state. This
786         * is indexed by <code>ColorType.getID()</code>.
787         *
788         * @return Array of colors
789         */
790        public Color[] getColors() {
791            return colors;
792        }
793
794        /**
795         * Returns the Color to used for the specified ColorType.
796         *
797         * @return Color.
798         */
799        public Color getColor(ColorType type) {
800            if (colors != null) {
801                int id = type.getID();
802
803                if (id < colors.length) {
804                    return colors[id];
805                }
806            }
807            return null;
808        }
809
810        /**
811         * Merges the contents of this StateInfo with that of the passed in
812         * StateInfo, returning the resulting merged StateInfo. Properties of
813         * this <code>StateInfo</code> will take precedence over those of the
814         * passed in <code>StateInfo</code>. For example, if this
815         * StateInfo specifics a non-null font, the returned StateInfo will
816         * have its font so to that regardless of the <code>StateInfo</code>'s
817         * font.
818         *
819         * @param info StateInfo to add our styles to
820         * @return Merged StateInfo.
821         */
822        public StateInfo addTo(StateInfo info) {
823            if (font != null) {
824                info.font = font;
825            }
826            if(data != null) {
827                if(info.data == null) {
828                    info.data = new HashMap<>();
829                }
830                info.data.putAll(data);
831            }
832            if (colors != null) {
833                if (info.colors == null) {
834                    info.colors = new Color[colors.length];
835                    System.arraycopy(colors, 0, info.colors, 0,
836                                     colors.length);
837                }
838                else {
839                    if (info.colors.length < colors.length) {
840                        Color[] old = info.colors;
841
842                        info.colors = new Color[colors.length];
843                        System.arraycopy(old, 0, info.colors, 0, old.length);
844                    }
845                    for (int counter = colors.length - 1; counter >= 0;
846                             counter--) {
847                        if (colors[counter] != null) {
848                            info.colors[counter] = colors[counter];
849                        }
850                    }
851                }
852            }
853            return info;
854        }
855
856        /**
857         * Sets the state this StateInfo corresponds to.
858         *
859         * @see SynthConstants
860         * @param state info.
861         */
862        public void setComponentState(int state) {
863            this.state = state;
864        }
865
866        /**
867         * Returns the state this StateInfo corresponds to.
868         *
869         * @see SynthConstants
870         * @return state info.
871         */
872        public int getComponentState() {
873            return state;
874        }
875
876        /**
877         * Creates and returns a copy of this StateInfo.
878         *
879         * @return Copy of this StateInfo.
880         */
881        public Object clone() {
882            return new StateInfo(this);
883        }
884
885        public String toString() {
886            StringBuilder sb = new StringBuilder();
887
888            sb.append(super.toString()).append(',');
889
890            sb.append("state=").append(Integer.toString(state)).append(',');
891
892            sb.append("font=").append(font).append(',');
893
894            if (colors != null) {
895                sb.append("colors=").append(Arrays.asList(colors)).
896                    append(',');
897            }
898            return sb.toString();
899        }
900    }
901}
902