Path: blob/master/src/java.desktop/share/classes/sun/swing/plaf/synth/DefaultSynthStyle.java
41161 views
/*1* Copyright (c) 2002, 2016, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package sun.swing.plaf.synth;2526import javax.swing.plaf.synth.*;27import java.awt.*;28import java.util.*;29import javax.swing.*;30import javax.swing.plaf.*;3132/**33* Default implementation of SynthStyle. Has setters for the various34* SynthStyle methods. Many of the properties can be specified for all states,35* using SynthStyle directly, or a specific state using one of the StateInfo36* methods.37* <p>38* Beyond the constructor a subclass should override the <code>addTo</code>39* and <code>clone</code> methods, these are used when the Styles are being40* merged into a resulting style.41*42* @author Scott Violet43*/44public class DefaultSynthStyle extends SynthStyle implements Cloneable {4546private static final Object PENDING = new Object();4748/**49* Should the component be opaque?50*/51private boolean opaque;52/**53* Insets.54*/55private Insets insets;56/**57* Information specific to ComponentState.58*/59private StateInfo[] states;60/**61* User specific data.62*/63private Map<Object, Object> data;6465/**66* Font to use if there is no matching StateInfo, or the StateInfo doesn't67* define one.68*/69private Font font;7071/**72* SynthGraphics, may be null.73*/74private SynthGraphicsUtils synthGraphics;7576/**77* Painter to use if the StateInfo doesn't have one.78*/79private SynthPainter painter;808182/**83* Nullary constructor, intended for subclassers.84*/85public DefaultSynthStyle() {86}8788/**89* Creates a new DefaultSynthStyle that is a copy of the passed in90* style. Any StateInfo's of the passed in style are clonsed as well.91*92* @param style Style to duplicate93*/94public DefaultSynthStyle(DefaultSynthStyle style) {95opaque = style.opaque;96if (style.insets != null) {97insets = new Insets(style.insets.top, style.insets.left,98style.insets.bottom, style.insets.right);99}100if (style.states != null) {101states = new StateInfo[style.states.length];102for (int counter = style.states.length - 1; counter >= 0;103counter--) {104states[counter] = (StateInfo)style.states[counter].clone();105}106}107if (style.data != null) {108data = new HashMap<>();109data.putAll(style.data);110}111font = style.font;112synthGraphics = style.synthGraphics;113painter = style.painter;114}115116/**117* Creates a new DefaultSynthStyle.118*119* @param insets Insets for the Style120* @param opaque Whether or not the background is completely painted in121* an opaque color122* @param states StateInfos describing properties per state123* @param data Style specific data.124*/125public DefaultSynthStyle(Insets insets, boolean opaque,126StateInfo[] states, Map<Object, Object> data) {127this.insets = insets;128this.opaque = opaque;129this.states = states;130this.data = data;131}132133public Color getColor(SynthContext context, ColorType type) {134return getColor(context.getComponent(), context.getRegion(),135context.getComponentState(), type);136}137138public Color getColor(JComponent c, Region id, int state,139ColorType type) {140// For the enabled state, prefer the widget's colors141if (!id.isSubregion() && state == SynthConstants.ENABLED) {142if (type == ColorType.BACKGROUND) {143return c.getBackground();144}145else if (type == ColorType.FOREGROUND) {146return c.getForeground();147}148else if (type == ColorType.TEXT_FOREGROUND) {149// If getForeground returns a non-UIResource it means the150// developer has explicitly set the foreground, use it over151// that of TEXT_FOREGROUND as that is typically the expected152// behavior.153Color color = c.getForeground();154if (!(color instanceof UIResource)) {155return color;156}157}158}159// Then use what we've locally defined160Color color = getColorForState(c, id, state, type);161if (color == null) {162// No color, fallback to that of the widget.163if (type == ColorType.BACKGROUND ||164type == ColorType.TEXT_BACKGROUND) {165return c.getBackground();166}167else if (type == ColorType.FOREGROUND ||168type == ColorType.TEXT_FOREGROUND) {169return c.getForeground();170}171}172return color;173}174175protected Color getColorForState(SynthContext context, ColorType type) {176return getColorForState(context.getComponent(), context.getRegion(),177context.getComponentState(), type);178}179180/**181* Returns the color for the specified state.182*183* @param c JComponent the style is associated with184* @param id Region identifier185* @param state State of the region.186* @param type Type of color being requested.187* @return Color to render with188*/189protected Color getColorForState(JComponent c, Region id, int state,190ColorType type) {191// Use the best state.192StateInfo si = getStateInfo(state);193Color color;194if (si != null && (color = si.getColor(type)) != null) {195return color;196}197if (si == null || si.getComponentState() != 0) {198si = getStateInfo(0);199if (si != null) {200return si.getColor(type);201}202}203return null;204}205206/**207* Sets the font that is used if there is no matching StateInfo, or208* it does not define a font.209*210* @param font Font to use for rendering211*/212public void setFont(Font font) {213this.font = font;214}215216public Font getFont(SynthContext state) {217return getFont(state.getComponent(), state.getRegion(),218state.getComponentState());219}220221public Font getFont(JComponent c, Region id, int state) {222if (!id.isSubregion() && state == SynthConstants.ENABLED) {223return c.getFont();224}225Font cFont = c.getFont();226if (cFont != null && !(cFont instanceof UIResource)) {227return cFont;228}229return getFontForState(c, id, state);230}231232/**233* Returns the font for the specified state. This should NOT callback234* to the JComponent.235*236* @param c JComponent the style is associated with237* @param id Region identifier238* @param state State of the region.239* @return Font to render with240*/241protected Font getFontForState(JComponent c, Region id, int state) {242if (c == null) {243return this.font;244}245// First pass, look for the best match246StateInfo si = getStateInfo(state);247Font font;248if (si != null && (font = si.getFont()) != null) {249return font;250}251if (si == null || si.getComponentState() != 0) {252si = getStateInfo(0);253if (si != null && (font = si.getFont()) != null) {254return font;255}256}257// Fallback font.258return this.font;259}260261protected Font getFontForState(SynthContext context) {262return getFontForState(context.getComponent(), context.getRegion(),263context.getComponentState());264}265266/**267* Sets the SynthGraphicsUtils that will be used for rendering.268*269* @param graphics SynthGraphics270*/271public void setGraphicsUtils(SynthGraphicsUtils graphics) {272this.synthGraphics = graphics;273}274275/**276* Returns a SynthGraphicsUtils.277*278* @param context SynthContext identifying requestor279* @return SynthGraphicsUtils280*/281public SynthGraphicsUtils getGraphicsUtils(SynthContext context) {282if (synthGraphics == null) {283return super.getGraphicsUtils(context);284}285return synthGraphics;286}287288/**289* Sets the insets.290*291* @param insets the new insets.292*/293public void setInsets(Insets insets) {294this.insets = insets;295}296297/**298* Returns the Insets. If <code>to</code> is non-null the resulting299* insets will be placed in it, otherwise a new Insets object will be300* created and returned.301*302* @param state SynthContext identifying requestor303* @param to Where to place Insets304* @return Insets.305*/306public Insets getInsets(SynthContext state, Insets to) {307if (to == null) {308to = new Insets(0, 0, 0, 0);309}310if (insets != null) {311to.left = insets.left;312to.right = insets.right;313to.top = insets.top;314to.bottom = insets.bottom;315}316else {317to.left = to.right = to.top = to.bottom = 0;318}319return to;320}321322/**323* Sets the Painter to use for the border.324*325* @param painter Painter for the Border.326*/327public void setPainter(SynthPainter painter) {328this.painter = painter;329}330331/**332* Returns the Painter for the passed in Component. This may return null.333*334* @param ss SynthContext identifying requestor335* @return Painter for the border336*/337public SynthPainter getPainter(SynthContext ss) {338return painter;339}340341/**342* Sets whether or not the JComponent should be opaque.343*344* @param opaque Whether or not the JComponent should be opaque.345*/346public void setOpaque(boolean opaque) {347this.opaque = opaque;348}349350/**351* Returns the value to initialize the opacity property of the Component352* to. A Style should NOT assume the opacity will remain this value, the353* developer may reset it or override it.354*355* @param ss SynthContext identifying requestor356* @return opaque Whether or not the JComponent is opaque.357*/358public boolean isOpaque(SynthContext ss) {359return opaque;360}361362/**363* Sets style specific values. This does NOT copy the data, it364* assigns it directly to this Style.365*366* @param data Style specific values367*/368public void setData(Map<Object, Object> data) {369this.data = data;370}371372/**373* Returns the style specific data.374*375* @return Style specific data.376*/377public Map<Object, Object> getData() {378return data;379}380381/**382* Getter for a region specific style property.383*384* @param state SynthContext identifying requestor385* @param key Property being requested.386* @return Value of the named property387*/388public Object get(SynthContext state, Object key) {389// Look for the best match390StateInfo si = getStateInfo(state.getComponentState());391if (si != null && si.getData() != null && getKeyFromData(si.getData(), key) != null) {392return getKeyFromData(si.getData(), key);393}394si = getStateInfo(0);395if (si != null && si.getData() != null && getKeyFromData(si.getData(), key) != null) {396return getKeyFromData(si.getData(), key);397}398if(getKeyFromData(data, key) != null)399return getKeyFromData(data, key);400return getDefaultValue(state, key);401}402403404private Object getKeyFromData(Map<Object, Object> stateData, Object key) {405Object value = null;406if (stateData != null) {407408synchronized(stateData) {409value = stateData.get(key);410}411while (value == PENDING) {412synchronized(stateData) {413try {414stateData.wait();415} catch (InterruptedException ie) {}416value = stateData.get(key);417}418}419if (value instanceof UIDefaults.LazyValue) {420synchronized(stateData) {421stateData.put(key, PENDING);422}423value = ((UIDefaults.LazyValue)value).createValue(null);424synchronized(stateData) {425stateData.put(key, value);426stateData.notifyAll();427}428}429}430return value;431}432433/**434* Returns the default value for a particular property. This is only435* invoked if this style doesn't define a property for <code>key</code>.436*437* @param context SynthContext identifying requestor438* @param key Property being requested.439* @return Value of the named property440*/441public Object getDefaultValue(SynthContext context, Object key) {442return super.get(context, key);443}444445/**446* Creates a clone of this style.447*448* @return Clone of this style449*/450public Object clone() {451DefaultSynthStyle style;452try {453style = (DefaultSynthStyle)super.clone();454} catch (CloneNotSupportedException cnse) {455return null;456}457if (states != null) {458style.states = new StateInfo[states.length];459for (int counter = states.length - 1; counter >= 0; counter--) {460style.states[counter] = (StateInfo)states[counter].clone();461}462}463if (data != null) {464style.data = new HashMap<>();465style.data.putAll(data);466}467return style;468}469470/**471* Merges the contents of this Style with that of the passed in Style,472* returning the resulting merged syle. Properties of this473* <code>DefaultSynthStyle</code> will take precedence over those of the474* passed in <code>DefaultSynthStyle</code>. For example, if this475* style specifics a non-null font, the returned style will have its476* font so to that regardless of the <code>style</code>'s font.477*478* @param style Style to add our styles to479* @return Merged style.480*/481public DefaultSynthStyle addTo(DefaultSynthStyle style) {482if (insets != null) {483style.insets = this.insets;484}485if (font != null) {486style.font = this.font;487}488if (painter != null) {489style.painter = this.painter;490}491if (synthGraphics != null) {492style.synthGraphics = this.synthGraphics;493}494style.opaque = opaque;495if (states != null) {496if (style.states == null) {497style.states = new StateInfo[states.length];498for (int counter = states.length - 1; counter >= 0; counter--){499if (states[counter] != null) {500style.states[counter] = (StateInfo)states[counter].501clone();502}503}504}505else {506// Find the number of new states in unique, merging any507// matching states as we go. Also, move any merge styles508// to the end to give them precedence.509int unique = 0;510// Number of StateInfos that match.511int matchCount = 0;512int maxOStyles = style.states.length;513for (int thisCounter = states.length - 1; thisCounter >= 0;514thisCounter--) {515int state = states[thisCounter].getComponentState();516boolean found = false;517518for (int oCounter = maxOStyles - 1 - matchCount;519oCounter >= 0; oCounter--) {520if (state == style.states[oCounter].521getComponentState()) {522style.states[oCounter] = states[thisCounter].523addTo(style.states[oCounter]);524// Move StateInfo to end, giving it precedence.525StateInfo tmp = style.states[maxOStyles - 1 -526matchCount];527style.states[maxOStyles - 1 - matchCount] =528style.states[oCounter];529style.states[oCounter] = tmp;530matchCount++;531found = true;532break;533}534}535if (!found) {536unique++;537}538}539if (unique != 0) {540// There are states that exist in this Style that541// don't exist in the other style, recreate the array542// and add them.543StateInfo[] newStates = new StateInfo[544unique + maxOStyles];545int newIndex = maxOStyles;546547System.arraycopy(style.states, 0, newStates, 0,maxOStyles);548for (int thisCounter = states.length - 1; thisCounter >= 0;549thisCounter--) {550int state = states[thisCounter].getComponentState();551boolean found = false;552553for (int oCounter = maxOStyles - 1; oCounter >= 0;554oCounter--) {555if (state == style.states[oCounter].556getComponentState()) {557found = true;558break;559}560}561if (!found) {562newStates[newIndex++] = (StateInfo)states[563thisCounter].clone();564}565}566style.states = newStates;567}568}569}570if (data != null) {571if (style.data == null) {572style.data = new HashMap<>();573}574style.data.putAll(data);575}576return style;577}578579/**580* Sets the array of StateInfo's which are used to specify properties581* specific to a particular style.582*583* @param states StateInfos584*/585public void setStateInfo(StateInfo[] states) {586this.states = states;587}588589/**590* Returns the array of StateInfo's that that are used to specify591* properties specific to a particular style.592*593* @return Array of StateInfos.594*/595public StateInfo[] getStateInfo() {596return states;597}598599/**600* Returns the best matching StateInfo for a particular state.601*602* @param state Component state.603* @return Best matching StateInfo, or null604*/605public StateInfo getStateInfo(int state) {606// Use the StateInfo with the most bits that matches that of state.607// If there is none, than fallback to608// the StateInfo with a state of 0, indicating it'll match anything.609610// Consider if we have 3 StateInfos a, b and c with states:611// SELECTED, SELECTED | ENABLED, 0612//613// Input Return Value614// ----- ------------615// SELECTED a616// SELECTED | ENABLED b617// MOUSE_OVER c618// SELECTED | ENABLED | FOCUSED b619// ENABLED c620621if (states != null) {622int bestCount = 0;623int bestIndex = -1;624int wildIndex = -1;625626if (state == 0) {627for (int counter = states.length - 1; counter >= 0;counter--) {628if (states[counter].getComponentState() == 0) {629return states[counter];630}631}632return null;633}634for (int counter = states.length - 1; counter >= 0; counter--) {635int oState = states[counter].getComponentState();636637if (oState == 0) {638if (wildIndex == -1) {639wildIndex = counter;640}641}642else if ((state & oState) == oState) {643// This is key, we need to make sure all bits of the644// StateInfo match, otherwise a StateInfo with645// SELECTED | ENABLED would match ENABLED, which we646// don't want.647int bitCount = Integer.bitCount(oState);648if (bitCount > bestCount) {649bestIndex = counter;650bestCount = bitCount;651}652}653}654if (bestIndex != -1) {655return states[bestIndex];656}657if (wildIndex != -1) {658return states[wildIndex];659}660}661return null;662}663664665public String toString() {666StringBuilder sb = new StringBuilder();667668sb.append(super.toString()).append(',');669670sb.append("data=").append(data).append(',');671672sb.append("font=").append(font).append(',');673674sb.append("insets=").append(insets).append(',');675676sb.append("synthGraphics=").append(synthGraphics).append(',');677678sb.append("painter=").append(painter).append(',');679680StateInfo[] states = getStateInfo();681if (states != null) {682sb.append("states[");683for (StateInfo state : states) {684sb.append(state.toString()).append(',');685}686sb.append(']').append(',');687}688689// remove last newline690sb.deleteCharAt(sb.length() - 1);691692return sb.toString();693}694695696/**697* StateInfo represents Style information specific to the state of698* a component.699*/700public static class StateInfo {701private Map<Object, Object> data;702private Font font;703private Color[] colors;704private int state;705706/**707* Creates a new StateInfo.708*/709public StateInfo() {710}711712/**713* Creates a new StateInfo with the specified properties714*715* @param state Component state(s) that this StateInfo should be used716* for717* @param font Font for this state718* @param colors Colors for this state719*/720public StateInfo(int state, Font font, Color[] colors) {721this.state = state;722this.font = font;723this.colors = colors;724}725726/**727* Creates a new StateInfo that is a copy of the passed in728* StateInfo.729*730* @param info StateInfo to copy.731*/732public StateInfo(StateInfo info) {733this.state = info.state;734this.font = info.font;735if(info.data != null) {736if(data == null) {737data = new HashMap<>();738}739data.putAll(info.data);740}741if (info.colors != null) {742this.colors = new Color[info.colors.length];743System.arraycopy(info.colors, 0, colors, 0,info.colors.length);744}745}746747public Map<Object, Object> getData() {748return data;749}750751public void setData(Map<Object, Object> data) {752this.data = data;753}754755/**756* Sets the font for this state.757*758* @param font Font to use for rendering759*/760public void setFont(Font font) {761this.font = font;762}763764/**765* Returns the font for this state.766*767* @return Returns the font to use for rendering this state768*/769public Font getFont() {770return font;771}772773/**774* Sets the array of colors to use for rendering this state. This775* is indexed by <code>ColorType.getID()</code>.776*777* @param colors Array of colors778*/779public void setColors(Color[] colors) {780this.colors = colors;781}782783/**784* Returns the array of colors to use for rendering this state. This785* is indexed by <code>ColorType.getID()</code>.786*787* @return Array of colors788*/789public Color[] getColors() {790return colors;791}792793/**794* Returns the Color to used for the specified ColorType.795*796* @return Color.797*/798public Color getColor(ColorType type) {799if (colors != null) {800int id = type.getID();801802if (id < colors.length) {803return colors[id];804}805}806return null;807}808809/**810* Merges the contents of this StateInfo with that of the passed in811* StateInfo, returning the resulting merged StateInfo. Properties of812* this <code>StateInfo</code> will take precedence over those of the813* passed in <code>StateInfo</code>. For example, if this814* StateInfo specifics a non-null font, the returned StateInfo will815* have its font so to that regardless of the <code>StateInfo</code>'s816* font.817*818* @param info StateInfo to add our styles to819* @return Merged StateInfo.820*/821public StateInfo addTo(StateInfo info) {822if (font != null) {823info.font = font;824}825if(data != null) {826if(info.data == null) {827info.data = new HashMap<>();828}829info.data.putAll(data);830}831if (colors != null) {832if (info.colors == null) {833info.colors = new Color[colors.length];834System.arraycopy(colors, 0, info.colors, 0,835colors.length);836}837else {838if (info.colors.length < colors.length) {839Color[] old = info.colors;840841info.colors = new Color[colors.length];842System.arraycopy(old, 0, info.colors, 0, old.length);843}844for (int counter = colors.length - 1; counter >= 0;845counter--) {846if (colors[counter] != null) {847info.colors[counter] = colors[counter];848}849}850}851}852return info;853}854855/**856* Sets the state this StateInfo corresponds to.857*858* @see SynthConstants859* @param state info.860*/861public void setComponentState(int state) {862this.state = state;863}864865/**866* Returns the state this StateInfo corresponds to.867*868* @see SynthConstants869* @return state info.870*/871public int getComponentState() {872return state;873}874875/**876* Creates and returns a copy of this StateInfo.877*878* @return Copy of this StateInfo.879*/880public Object clone() {881return new StateInfo(this);882}883884public String toString() {885StringBuilder sb = new StringBuilder();886887sb.append(super.toString()).append(',');888889sb.append("state=").append(Integer.toString(state)).append(',');890891sb.append("font=").append(font).append(',');892893if (colors != null) {894sb.append("colors=").append(Arrays.asList(colors)).895append(',');896}897return sb.toString();898}899}900}901902903