blob: 5fc577803ee601985a6f535d2b5b37f2781bf92b [file] [log] [blame]
/*
* Copyright (c) 2002, 2008, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.swing.plaf.synth;
import javax.swing.plaf.synth.*;
import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.*;
/**
* Default implementation of SynthStyle. Has setters for the various
* SynthStyle methods. Many of the properties can be specified for all states,
* using SynthStyle directly, or a specific state using one of the StateInfo
* methods.
* <p>
* Beyond the constructor a subclass should override the <code>addTo</code>
* and <code>clone</code> methods, these are used when the Styles are being
* merged into a resulting style.
*
* @author Scott Violet
*/
public class DefaultSynthStyle extends SynthStyle implements Cloneable {
private static final String PENDING = "Pending";
/**
* Should the component be opaque?
*/
private boolean opaque;
/**
* Insets.
*/
private Insets insets;
/**
* Information specific to ComponentState.
*/
private StateInfo[] states;
/**
* User specific data.
*/
private Map data;
/**
* Font to use if there is no matching StateInfo, or the StateInfo doesn't
* define one.
*/
private Font font;
/**
* SynthGraphics, may be null.
*/
private SynthGraphicsUtils synthGraphics;
/**
* Painter to use if the StateInfo doesn't have one.
*/
private SynthPainter painter;
/**
* Nullary constructor, intended for subclassers.
*/
public DefaultSynthStyle() {
}
/**
* Creates a new DefaultSynthStyle that is a copy of the passed in
* style. Any StateInfo's of the passed in style are clonsed as well.
*
* @param style Style to duplicate
*/
public DefaultSynthStyle(DefaultSynthStyle style) {
opaque = style.opaque;
if (style.insets != null) {
insets = new Insets(style.insets.top, style.insets.left,
style.insets.bottom, style.insets.right);
}
if (style.states != null) {
states = new StateInfo[style.states.length];
for (int counter = style.states.length - 1; counter >= 0;
counter--) {
states[counter] = (StateInfo)style.states[counter].clone();
}
}
if (style.data != null) {
data = new HashMap();
data.putAll(style.data);
}
font = style.font;
synthGraphics = style.synthGraphics;
painter = style.painter;
}
/**
* Creates a new DefaultSynthStyle.
*
* @param insets Insets for the Style
* @param opaque Whether or not the background is completely painted in
* an opaque color
* @param states StateInfos describing properties per state
* @param data Style specific data.
*/
public DefaultSynthStyle(Insets insets, boolean opaque,
StateInfo[] states, Map data) {
this.insets = insets;
this.opaque = opaque;
this.states = states;
this.data = data;
}
public Color getColor(SynthContext context, ColorType type) {
return getColor(context.getComponent(), context.getRegion(),
context.getComponentState(), type);
}
public Color getColor(JComponent c, Region id, int state,
ColorType type) {
// For the enabled state, prefer the widget's colors
if (!id.isSubregion() && state == SynthConstants.ENABLED) {
if (type == ColorType.BACKGROUND) {
return c.getBackground();
}
else if (type == ColorType.FOREGROUND) {
return c.getForeground();
}
else if (type == ColorType.TEXT_FOREGROUND) {
// If getForeground returns a non-UIResource it means the
// developer has explicitly set the foreground, use it over
// that of TEXT_FOREGROUND as that is typically the expected
// behavior.
Color color = c.getForeground();
if (!(color instanceof UIResource)) {
return color;
}
}
}
// Then use what we've locally defined
Color color = getColorForState(c, id, state, type);
if (color == null) {
// No color, fallback to that of the widget.
if (type == ColorType.BACKGROUND ||
type == ColorType.TEXT_BACKGROUND) {
return c.getBackground();
}
else if (type == ColorType.FOREGROUND ||
type == ColorType.TEXT_FOREGROUND) {
return c.getForeground();
}
}
return color;
}
protected Color getColorForState(SynthContext context, ColorType type) {
return getColorForState(context.getComponent(), context.getRegion(),
context.getComponentState(), type);
}
/**
* Returns the color for the specified state.
*
* @param c JComponent the style is associated with
* @param id Region identifier
* @param state State of the region.
* @param type Type of color being requested.
* @return Color to render with
*/
protected Color getColorForState(JComponent c, Region id, int state,
ColorType type) {
// Use the best state.
StateInfo si = getStateInfo(state);
Color color;
if (si != null && (color = si.getColor(type)) != null) {
return color;
}
if (si == null || si.getComponentState() != 0) {
si = getStateInfo(0);
if (si != null) {
return si.getColor(type);
}
}
return null;
}
/**
* Sets the font that is used if there is no matching StateInfo, or
* it does not define a font.
*
* @param font Font to use for rendering
*/
public void setFont(Font font) {
this.font = font;
}
public Font getFont(SynthContext state) {
return getFont(state.getComponent(), state.getRegion(),
state.getComponentState());
}
public Font getFont(JComponent c, Region id, int state) {
if (!id.isSubregion() && state == SynthConstants.ENABLED) {
return c.getFont();
}
Font cFont = c.getFont();
if (cFont != null && !(cFont instanceof UIResource)) {
return cFont;
}
return getFontForState(c, id, state);
}
/**
* Returns the font for the specified state. This should NOT callback
* to the JComponent.
*
* @param c JComponent the style is associated with
* @param id Region identifier
* @param state State of the region.
* @return Font to render with
*/
protected Font getFontForState(JComponent c, Region id, int state) {
if (c == null) {
return this.font;
}
// First pass, look for the best match
StateInfo si = getStateInfo(state);
Font font;
if (si != null && (font = si.getFont()) != null) {
return font;
}
if (si == null || si.getComponentState() != 0) {
si = getStateInfo(0);
if (si != null && (font = si.getFont()) != null) {
return font;
}
}
// Fallback font.
return this.font;
}
protected Font getFontForState(SynthContext context) {
return getFontForState(context.getComponent(), context.getRegion(),
context.getComponentState());
}
/**
* Sets the SynthGraphicsUtils that will be used for rendering.
*
* @param graphics SynthGraphics
*/
public void setGraphicsUtils(SynthGraphicsUtils graphics) {
this.synthGraphics = graphics;
}
/**
* Returns a SynthGraphicsUtils.
*
* @param context SynthContext indentifying requestor
* @return SynthGraphicsUtils
*/
public SynthGraphicsUtils getGraphicsUtils(SynthContext context) {
if (synthGraphics == null) {
return super.getGraphicsUtils(context);
}
return synthGraphics;
}
/**
* Sets the insets.
*
* @param Insets.
*/
public void setInsets(Insets insets) {
this.insets = insets;
}
/**
* Returns the Insets. If <code>to</code> is non-null the resulting
* insets will be placed in it, otherwise a new Insets object will be
* created and returned.
*
* @param context SynthContext indentifying requestor
* @param to Where to place Insets
* @return Insets.
*/
public Insets getInsets(SynthContext state, Insets to) {
if (to == null) {
to = new Insets(0, 0, 0, 0);
}
if (insets != null) {
to.left = insets.left;
to.right = insets.right;
to.top = insets.top;
to.bottom = insets.bottom;
}
else {
to.left = to.right = to.top = to.bottom = 0;
}
return to;
}
/**
* Sets the Painter to use for the border.
*
* @param painter Painter for the Border.
*/
public void setPainter(SynthPainter painter) {
this.painter = painter;
}
/**
* Returns the Painter for the passed in Component. This may return null.
*
* @param ss SynthContext indentifying requestor
* @return Painter for the border
*/
public SynthPainter getPainter(SynthContext ss) {
return painter;
}
/**
* Sets whether or not the JComponent should be opaque.
*
* @param opaque Whether or not the JComponent should be opaque.
*/
public void setOpaque(boolean opaque) {
this.opaque = opaque;
}
/**
* Returns the value to initialize the opacity property of the Component
* to. A Style should NOT assume the opacity will remain this value, the
* developer may reset it or override it.
*
* @param ss SynthContext indentifying requestor
* @return opaque Whether or not the JComponent is opaque.
*/
public boolean isOpaque(SynthContext ss) {
return opaque;
}
/**
* Sets style specific values. This does NOT copy the data, it
* assigns it directly to this Style.
*
* @param data Style specific values
*/
public void setData(Map data) {
this.data = data;
}
/**
* Returns the style specific data.
*
* @return Style specific data.
*/
public Map getData() {
return data;
}
/**
* Getter for a region specific style property.
*
* @param state SynthContext indentifying requestor
* @param key Property being requested.
* @return Value of the named property
*/
public Object get(SynthContext state, Object key) {
// Look for the best match
StateInfo si = getStateInfo(state.getComponentState());
if (si != null && si.getData() != null && getKeyFromData(si.getData(), key) != null) {
return getKeyFromData(si.getData(), key);
}
si = getStateInfo(0);
if (si != null && si.getData() != null && getKeyFromData(si.getData(), key) != null) {
return getKeyFromData(si.getData(), key);
}
if(getKeyFromData(data, key) != null)
return getKeyFromData(data, key);
return getDefaultValue(state, key);
}
private Object getKeyFromData(Map stateData, Object key) {
Object value = null;
if (stateData != null) {
synchronized(stateData) {
value = stateData.get(key);
}
while (value == PENDING) {
synchronized(stateData) {
try {
stateData.wait();
} catch (InterruptedException ie) {}
value = stateData.get(key);
}
}
if (value instanceof UIDefaults.LazyValue) {
synchronized(stateData) {
stateData.put(key, PENDING);
}
value = ((UIDefaults.LazyValue)value).createValue(null);
synchronized(stateData) {
stateData.put(key, value);
stateData.notifyAll();
}
}
}
return value;
}
/**
* Returns the default value for a particular property. This is only
* invoked if this style doesn't define a property for <code>key</code>.
*
* @param state SynthContext indentifying requestor
* @param key Property being requested.
* @return Value of the named property
*/
public Object getDefaultValue(SynthContext context, Object key) {
return super.get(context, key);
}
/**
* Creates a clone of this style.
*
* @return Clone of this style
*/
public Object clone() {
DefaultSynthStyle style;
try {
style = (DefaultSynthStyle)super.clone();
} catch (CloneNotSupportedException cnse) {
return null;
}
if (states != null) {
style.states = new StateInfo[states.length];
for (int counter = states.length - 1; counter >= 0; counter--) {
style.states[counter] = (StateInfo)states[counter].clone();
}
}
if (data != null) {
style.data = new HashMap();
style.data.putAll(data);
}
return style;
}
/**
* Merges the contents of this Style with that of the passed in Style,
* returning the resulting merged syle. Properties of this
* <code>DefaultSynthStyle</code> will take precedence over those of the
* passed in <code>DefaultSynthStyle</code>. For example, if this
* style specifics a non-null font, the returned style will have its
* font so to that regardless of the <code>style</code>'s font.
*
* @param style Style to add our styles to
* @return Merged style.
*/
public DefaultSynthStyle addTo(DefaultSynthStyle style) {
if (insets != null) {
style.insets = this.insets;
}
if (font != null) {
style.font = this.font;
}
if (painter != null) {
style.painter = this.painter;
}
if (synthGraphics != null) {
style.synthGraphics = this.synthGraphics;
}
style.opaque = opaque;
if (states != null) {
if (style.states == null) {
style.states = new StateInfo[states.length];
for (int counter = states.length - 1; counter >= 0; counter--){
if (states[counter] != null) {
style.states[counter] = (StateInfo)states[counter].
clone();
}
}
}
else {
// Find the number of new states in unique, merging any
// matching states as we go. Also, move any merge styles
// to the end to give them precedence.
int unique = 0;
// Number of StateInfos that match.
int matchCount = 0;
int maxOStyles = style.states.length;
for (int thisCounter = states.length - 1; thisCounter >= 0;
thisCounter--) {
int state = states[thisCounter].getComponentState();
boolean found = false;
for (int oCounter = maxOStyles - 1 - matchCount;
oCounter >= 0; oCounter--) {
if (state == style.states[oCounter].
getComponentState()) {
style.states[oCounter] = states[thisCounter].
addTo(style.states[oCounter]);
// Move StateInfo to end, giving it precedence.
StateInfo tmp = style.states[maxOStyles - 1 -
matchCount];
style.states[maxOStyles - 1 - matchCount] =
style.states[oCounter];
style.states[oCounter] = tmp;
matchCount++;
found = true;
break;
}
}
if (!found) {
unique++;
}
}
if (unique != 0) {
// There are states that exist in this Style that
// don't exist in the other style, recreate the array
// and add them.
StateInfo[] newStates = new StateInfo[
unique + maxOStyles];
int newIndex = maxOStyles;
System.arraycopy(style.states, 0, newStates, 0,maxOStyles);
for (int thisCounter = states.length - 1; thisCounter >= 0;
thisCounter--) {
int state = states[thisCounter].getComponentState();
boolean found = false;
for (int oCounter = maxOStyles - 1; oCounter >= 0;
oCounter--) {
if (state == style.states[oCounter].
getComponentState()) {
found = true;
break;
}
}
if (!found) {
newStates[newIndex++] = (StateInfo)states[
thisCounter].clone();
}
}
style.states = newStates;
}
}
}
if (data != null) {
if (style.data == null) {
style.data = new HashMap();
}
style.data.putAll(data);
}
return style;
}
/**
* Sets the array of StateInfo's which are used to specify properties
* specific to a particular style.
*
* @param states StateInfos
*/
public void setStateInfo(StateInfo[] states) {
this.states = states;
}
/**
* Returns the array of StateInfo's that that are used to specify
* properties specific to a particular style.
*
* @return Array of StateInfos.
*/
public StateInfo[] getStateInfo() {
return states;
}
/**
* Returns the best matching StateInfo for a particular state.
*
* @param state Component state.
* @return Best matching StateInfo, or null
*/
public StateInfo getStateInfo(int state) {
// Use the StateInfo with the most bits that matches that of state.
// If there is none, than fallback to
// the StateInfo with a state of 0, indicating it'll match anything.
// Consider if we have 3 StateInfos a, b and c with states:
// SELECTED, SELECTED | ENABLED, 0
//
// Input Return Value
// ----- ------------
// SELECTED a
// SELECTED | ENABLED b
// MOUSE_OVER c
// SELECTED | ENABLED | FOCUSED b
// ENABLED c
if (states != null) {
int bestCount = 0;
int bestIndex = -1;
int wildIndex = -1;
if (state == 0) {
for (int counter = states.length - 1; counter >= 0;counter--) {
if (states[counter].getComponentState() == 0) {
return states[counter];
}
}
return null;
}
for (int counter = states.length - 1; counter >= 0; counter--) {
int oState = states[counter].getComponentState();
if (oState == 0) {
if (wildIndex == -1) {
wildIndex = counter;
}
}
else if ((state & oState) == oState) {
// This is key, we need to make sure all bits of the
// StateInfo match, otherwise a StateInfo with
// SELECTED | ENABLED would match ENABLED, which we
// don't want.
// This comes from BigInteger.bitCnt
int bitCount = oState;
bitCount -= (0xaaaaaaaa & bitCount) >>> 1;
bitCount = (bitCount & 0x33333333) + ((bitCount >>> 2) &
0x33333333);
bitCount = bitCount + (bitCount >>> 4) & 0x0f0f0f0f;
bitCount += bitCount >>> 8;
bitCount += bitCount >>> 16;
bitCount = bitCount & 0xff;
if (bitCount > bestCount) {
bestIndex = counter;
bestCount = bitCount;
}
}
}
if (bestIndex != -1) {
return states[bestIndex];
}
if (wildIndex != -1) {
return states[wildIndex];
}
}
return null;
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append(super.toString()).append(',');
buf.append("data=").append(data).append(',');
buf.append("font=").append(font).append(',');
buf.append("insets=").append(insets).append(',');
buf.append("synthGraphics=").append(synthGraphics).append(',');
buf.append("painter=").append(painter).append(',');
StateInfo[] states = getStateInfo();
if (states != null) {
buf.append("states[");
for (StateInfo state : states) {
buf.append(state.toString()).append(',');
}
buf.append(']').append(',');
}
// remove last newline
buf.deleteCharAt(buf.length() - 1);
return buf.toString();
}
/**
* StateInfo represents Style information specific to the state of
* a component.
*/
public static class StateInfo {
private Map data;
private Font font;
private Color[] colors;
private int state;
/**
* Creates a new StateInfo.
*/
public StateInfo() {
}
/**
* Creates a new StateInfo with the specified properties
*
* @param state Component state(s) that this StateInfo should be used
* for
* @param painter Painter responsible for rendering
* @param bgPainter Painter responsible for rendering the background
* @param font Font for this state
* @param colors Colors for this state
*/
public StateInfo(int state, Font font, Color[] colors) {
this.state = state;
this.font = font;
this.colors = colors;
}
/**
* Creates a new StateInfo that is a copy of the passed in
* StateInfo.
*
* @param info StateInfo to copy.
*/
public StateInfo(StateInfo info) {
this.state = info.state;
this.font = info.font;
if(info.data != null) {
if(data == null) {
data = new HashMap();
}
data.putAll(info.data);
}
if (info.colors != null) {
this.colors = new Color[info.colors.length];
System.arraycopy(info.colors, 0, colors, 0,info.colors.length);
}
}
public Map getData() {
return data;
}
public void setData(Map data) {
this.data = data;
}
/**
* Sets the font for this state.
*
* @param font Font to use for rendering
*/
public void setFont(Font font) {
this.font = font;
}
/**
* Returns the font for this state.
*
* @return Returns the font to use for rendering this state
*/
public Font getFont() {
return font;
}
/**
* Sets the array of colors to use for rendering this state. This
* is indexed by <code>ColorType.getID()</code>.
*
* @param colors Array of colors
*/
public void setColors(Color[] colors) {
this.colors = colors;
}
/**
* Returns the array of colors to use for rendering this state. This
* is indexed by <code>ColorType.getID()</code>.
*
* @return Array of colors
*/
public Color[] getColors() {
return colors;
}
/**
* Returns the Color to used for the specified ColorType.
*
* @return Color.
*/
public Color getColor(ColorType type) {
if (colors != null) {
int id = type.getID();
if (id < colors.length) {
return colors[id];
}
}
return null;
}
/**
* Merges the contents of this StateInfo with that of the passed in
* StateInfo, returning the resulting merged StateInfo. Properties of
* this <code>StateInfo</code> will take precedence over those of the
* passed in <code>StateInfo</code>. For example, if this
* StateInfo specifics a non-null font, the returned StateInfo will
* have its font so to that regardless of the <code>StateInfo</code>'s
* font.
*
* @param info StateInfo to add our styles to
* @return Merged StateInfo.
*/
public StateInfo addTo(StateInfo info) {
if (font != null) {
info.font = font;
}
if(data != null) {
if(info.data == null) {
info.data = new HashMap();
}
info.data.putAll(data);
}
if (colors != null) {
if (info.colors == null) {
info.colors = new Color[colors.length];
System.arraycopy(colors, 0, info.colors, 0,
colors.length);
}
else {
if (info.colors.length < colors.length) {
Color[] old = info.colors;
info.colors = new Color[colors.length];
System.arraycopy(old, 0, info.colors, 0, old.length);
}
for (int counter = colors.length - 1; counter >= 0;
counter--) {
if (colors[counter] != null) {
info.colors[counter] = colors[counter];
}
}
}
}
return info;
}
/**
* Sets the state this StateInfo corresponds to.
*
* @see SynthConstants
* @param state info.
*/
public void setComponentState(int state) {
this.state = state;
}
/**
* Returns the state this StateInfo corresponds to.
*
* @see SynthConstants
* @return state info.
*/
public int getComponentState() {
return state;
}
/**
* Returns the number of states that are similar between the
* ComponentState this StateInfo represents and val.
*/
private int getMatchCount(int val) {
// This comes from BigInteger.bitCnt
val &= state;
val -= (0xaaaaaaaa & val) >>> 1;
val = (val & 0x33333333) + ((val >>> 2) & 0x33333333);
val = val + (val >>> 4) & 0x0f0f0f0f;
val += val >>> 8;
val += val >>> 16;
return val & 0xff;
}
/**
* Creates and returns a copy of this StateInfo.
*
* @return Copy of this StateInfo.
*/
public Object clone() {
return new StateInfo(this);
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append(super.toString()).append(',');
buf.append("state=").append(Integer.toString(state)).append(',');
buf.append("font=").append(font).append(',');
if (colors != null) {
buf.append("colors=").append(Arrays.asList(colors)).
append(',');
}
return buf.toString();
}
}
}