/*
* Copyright (c) 2002, 2010, 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 javax.swing.plaf.synth;
import java.awt.*;
import java.beans.*;
import java.io.*;
import java.lang.ref.*;
import java.net.*;
import java.security.*;
import java.text.*;
import java.util.*;
import javax.swing.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.*;
import sun.awt.*;
import sun.security.action.*;
import sun.swing.*;
import sun.swing.plaf.synth.*;
/**
* SynthLookAndFeel provides the basis for creating a customized look and
* feel. SynthLookAndFeel does not directly provide a look, all painting is
* delegated.
* You need to either provide a configuration file, by way of the
* {@link #load} method, or provide your own {@link SynthStyleFactory}
* to {@link #setStyleFactory}. Refer to the
* package summary for an example of
* loading a file, and {@link javax.swing.plaf.synth.SynthStyleFactory} for
* an example of providing your own SynthStyleFactory
to
* setStyleFactory
.
*
* Warning:
* This class implements {@link Serializable} as a side effect of it
* extending {@link BasicLookAndFeel}. It is not intended to be serialized.
* An attempt to serialize it will
* result in {@link NotSerializableException}.
*
* @serial exclude
* @since 1.5
* @author Scott Violet
*/
public class SynthLookAndFeel extends BasicLookAndFeel {
/**
* Used in a handful of places where we need an empty Insets.
*/
static final Insets EMPTY_UIRESOURCE_INSETS = new InsetsUIResource(
0, 0, 0, 0);
/**
* AppContext key to get the current SynthStyleFactory.
*/
private static final Object STYLE_FACTORY_KEY =
new StringBuffer("com.sun.java.swing.plaf.gtk.StyleCache");
/**
* AppContext key to get selectedUI.
*/
private static final Object SELECTED_UI_KEY = new StringBuilder("selectedUI");
/**
* AppContext key to get selectedUIState.
*/
private static final Object SELECTED_UI_STATE_KEY = new StringBuilder("selectedUIState");
/**
* The last SynthStyleFactory that was asked for from AppContext
*
* For the returned lastContext
.
*/
private static SynthStyleFactory lastFactory;
/**
* AppContext lastLAF came from.
*/
private static AppContext lastContext;
/**
* SynthStyleFactory for the this SynthLookAndFeel.
*/
private SynthStyleFactory factory;
/**
* Map of defaults table entries. This is populated via the load
* method.
*/
private MapselectedUI
(which this methods sets), if
* it does, then a state as set by this method is returned. This provides
* a way for labels to have a state other than selected.
*/
static void setSelectedUI(ComponentUI uix, boolean selected,
boolean focused, boolean enabled,
boolean rollover) {
int selectedUIState = 0;
if (selected) {
selectedUIState = SynthConstants.SELECTED;
if (focused) {
selectedUIState |= SynthConstants.FOCUSED;
}
}
else if (rollover && enabled) {
selectedUIState |=
SynthConstants.MOUSE_OVER | SynthConstants.ENABLED;
if (focused) {
selectedUIState |= SynthConstants.FOCUSED;
}
}
else {
if (enabled) {
selectedUIState |= SynthConstants.ENABLED;
if (focused) {
selectedUIState |= SynthConstants.FOCUSED;
}
}
else {
selectedUIState |= SynthConstants.DISABLED;
}
}
AppContext context = AppContext.getAppContext();
context.put(SELECTED_UI_KEY, uix);
context.put(SELECTED_UI_STATE_KEY, Integer.valueOf(selectedUIState));
}
static int getSelectedUIState() {
Integer result = (Integer) AppContext.getAppContext().get(SELECTED_UI_STATE_KEY);
return result == null ? 0 : result.intValue();
}
/**
* Clears out the selected UI that was last set in setSelectedUI.
*/
static void resetSelectedUI() {
AppContext.getAppContext().remove(SELECTED_UI_KEY);
}
/**
* Sets the SynthStyleFactory that the UI classes provided by
* synth will use to obtain a SynthStyle.
*
* @param cache SynthStyleFactory the UIs should use.
*/
public static void setStyleFactory(SynthStyleFactory cache) {
// We assume the setter is called BEFORE the getter has been invoked
// for a particular AppContext.
synchronized(SynthLookAndFeel.class) {
AppContext context = AppContext.getAppContext();
lastFactory = cache;
lastContext = context;
context.put(STYLE_FACTORY_KEY, cache);
}
}
/**
* Returns the current SynthStyleFactory.
*
* @return SynthStyleFactory
*/
public static SynthStyleFactory getStyleFactory() {
synchronized(SynthLookAndFeel.class) {
AppContext context = AppContext.getAppContext();
if (lastContext == context) {
return lastFactory;
}
lastContext = context;
lastFactory = (SynthStyleFactory) context.get(STYLE_FACTORY_KEY);
return lastFactory;
}
}
/**
* Returns the component state for the specified component. This should
* only be used for Components that don't have any special state beyond
* that of ENABLED, DISABLED or FOCUSED. For example, buttons shouldn't
* call into this method.
*/
static int getComponentState(Component c) {
if (c.isEnabled()) {
if (c.isFocusOwner()) {
return SynthUI.ENABLED | SynthUI.FOCUSED;
}
return SynthUI.ENABLED;
}
return SynthUI.DISABLED;
}
/**
* Gets a SynthStyle for the specified region of the specified component.
* This is not for general consumption, only custom UIs should call this
* method.
*
* @param c JComponent to get the SynthStyle for
* @param region Identifies the region of the specified component
* @return SynthStyle to use.
*/
public static SynthStyle getStyle(JComponent c, Region region) {
return getStyleFactory().getStyle(c, region);
}
/**
* Returns true if the Style should be updated in response to the
* specified PropertyChangeEvent. This forwards to
* shouldUpdateStyleOnAncestorChanged
as necessary.
*/
static boolean shouldUpdateStyle(PropertyChangeEvent event) {
LookAndFeel laf = UIManager.getLookAndFeel();
return (laf instanceof SynthLookAndFeel &&
((SynthLookAndFeel) laf).shouldUpdateStyleOnEvent(event));
}
/**
* A convience method that will reset the Style of StyleContext if
* necessary.
*
* @return newStyle
*/
static SynthStyle updateStyle(SynthContext context, SynthUI ui) {
SynthStyle newStyle = getStyle(context.getComponent(),
context.getRegion());
SynthStyle oldStyle = context.getStyle();
if (newStyle != oldStyle) {
if (oldStyle != null) {
oldStyle.uninstallDefaults(context);
}
context.setStyle(newStyle);
newStyle.installDefaults(context, ui);
}
return newStyle;
}
/**
* Updates the style associated with c
, and all its children.
* This is a lighter version of
* SwingUtilities.updateComponentTreeUI
.
*
* @param c Component to update style for.
*/
public static void updateStyles(Component c) {
if (c instanceof JComponent) {
// Yes, this is hacky. A better solution is to get the UI
// and cast, but JComponent doesn't expose a getter for the UI
// (each of the UIs do), making that approach impractical.
String name = c.getName();
c.setName(null);
if (name != null) {
c.setName(name);
}
((JComponent)c).revalidate();
}
Component[] children = null;
if (c instanceof JMenu) {
children = ((JMenu)c).getMenuComponents();
}
else if (c instanceof Container) {
children = ((Container)c).getComponents();
}
if (children != null) {
for (Component child : children) {
updateStyles(child);
}
}
c.repaint();
}
/**
* Returns the Region for the JComponent c
.
*
* @param c JComponent to fetch the Region for
* @return Region corresponding to c
*/
public static Region getRegion(JComponent c) {
return Region.getRegion(c);
}
/**
* A convenience method to return where the foreground should be
* painted for the Component identified by the passed in
* AbstractSynthContext.
*/
static Insets getPaintingInsets(SynthContext state, Insets insets) {
if (state.isSubregion()) {
insets = state.getStyle().getInsets(state, insets);
}
else {
insets = state.getComponent().getInsets(insets);
}
return insets;
}
/**
* A convenience method that handles painting of the background.
* All SynthUI implementations should override update and invoke
* this method.
*/
static void update(SynthContext state, Graphics g) {
paintRegion(state, g, null);
}
/**
* A convenience method that handles painting of the background for
* subregions. All SynthUI's that have subregions should invoke
* this method, than paint the foreground.
*/
static void updateSubregion(SynthContext state, Graphics g,
Rectangle bounds) {
paintRegion(state, g, bounds);
}
private static void paintRegion(SynthContext state, Graphics g,
Rectangle bounds) {
JComponent c = state.getComponent();
SynthStyle style = state.getStyle();
int x, y, width, height;
if (bounds == null) {
x = 0;
y = 0;
width = c.getWidth();
height = c.getHeight();
}
else {
x = bounds.x;
y = bounds.y;
width = bounds.width;
height = bounds.height;
}
// Fill in the background, if necessary.
boolean subregion = state.isSubregion();
if ((subregion && style.isOpaque(state)) ||
(!subregion && c.isOpaque())) {
g.setColor(style.getColor(state, ColorType.BACKGROUND));
g.fillRect(x, y, width, height);
}
}
static boolean isLeftToRight(Component c) {
return c.getComponentOrientation().isLeftToRight();
}
/**
* Returns the ui that is of type klass
, or null if
* one can not be found.
*/
static Object getUIOfType(ComponentUI ui, Class klass) {
if (klass.isInstance(ui)) {
return ui;
}
return null;
}
/**
* Creates the Synth look and feel ComponentUI
for
* the passed in JComponent
.
*
* @param c JComponent to create the ComponentUI
for
* @return ComponentUI to use for c
*/
public static ComponentUI createUI(JComponent c) {
String key = c.getUIClassID().intern();
if (key == "ButtonUI") {
return SynthButtonUI.createUI(c);
}
else if (key == "CheckBoxUI") {
return SynthCheckBoxUI.createUI(c);
}
else if (key == "CheckBoxMenuItemUI") {
return SynthCheckBoxMenuItemUI.createUI(c);
}
else if (key == "ColorChooserUI") {
return SynthColorChooserUI.createUI(c);
}
else if (key == "ComboBoxUI") {
return SynthComboBoxUI.createUI(c);
}
else if (key == "DesktopPaneUI") {
return SynthDesktopPaneUI.createUI(c);
}
else if (key == "DesktopIconUI") {
return SynthDesktopIconUI.createUI(c);
}
else if (key == "EditorPaneUI") {
return SynthEditorPaneUI.createUI(c);
}
else if (key == "FileChooserUI") {
return SynthFileChooserUI.createUI(c);
}
else if (key == "FormattedTextFieldUI") {
return SynthFormattedTextFieldUI.createUI(c);
}
else if (key == "InternalFrameUI") {
return SynthInternalFrameUI.createUI(c);
}
else if (key == "LabelUI") {
return SynthLabelUI.createUI(c);
}
else if (key == "ListUI") {
return SynthListUI.createUI(c);
}
else if (key == "MenuBarUI") {
return SynthMenuBarUI.createUI(c);
}
else if (key == "MenuUI") {
return SynthMenuUI.createUI(c);
}
else if (key == "MenuItemUI") {
return SynthMenuItemUI.createUI(c);
}
else if (key == "OptionPaneUI") {
return SynthOptionPaneUI.createUI(c);
}
else if (key == "PanelUI") {
return SynthPanelUI.createUI(c);
}
else if (key == "PasswordFieldUI") {
return SynthPasswordFieldUI.createUI(c);
}
else if (key == "PopupMenuSeparatorUI") {
return SynthSeparatorUI.createUI(c);
}
else if (key == "PopupMenuUI") {
return SynthPopupMenuUI.createUI(c);
}
else if (key == "ProgressBarUI") {
return SynthProgressBarUI.createUI(c);
}
else if (key == "RadioButtonUI") {
return SynthRadioButtonUI.createUI(c);
}
else if (key == "RadioButtonMenuItemUI") {
return SynthRadioButtonMenuItemUI.createUI(c);
}
else if (key == "RootPaneUI") {
return SynthRootPaneUI.createUI(c);
}
else if (key == "ScrollBarUI") {
return SynthScrollBarUI.createUI(c);
}
else if (key == "ScrollPaneUI") {
return SynthScrollPaneUI.createUI(c);
}
else if (key == "SeparatorUI") {
return SynthSeparatorUI.createUI(c);
}
else if (key == "SliderUI") {
return SynthSliderUI.createUI(c);
}
else if (key == "SpinnerUI") {
return SynthSpinnerUI.createUI(c);
}
else if (key == "SplitPaneUI") {
return SynthSplitPaneUI.createUI(c);
}
else if (key == "TabbedPaneUI") {
return SynthTabbedPaneUI.createUI(c);
}
else if (key == "TableUI") {
return SynthTableUI.createUI(c);
}
else if (key == "TableHeaderUI") {
return SynthTableHeaderUI.createUI(c);
}
else if (key == "TextAreaUI") {
return SynthTextAreaUI.createUI(c);
}
else if (key == "TextFieldUI") {
return SynthTextFieldUI.createUI(c);
}
else if (key == "TextPaneUI") {
return SynthTextPaneUI.createUI(c);
}
else if (key == "ToggleButtonUI") {
return SynthToggleButtonUI.createUI(c);
}
else if (key == "ToolBarSeparatorUI") {
return SynthSeparatorUI.createUI(c);
}
else if (key == "ToolBarUI") {
return SynthToolBarUI.createUI(c);
}
else if (key == "ToolTipUI") {
return SynthToolTipUI.createUI(c);
}
else if (key == "TreeUI") {
return SynthTreeUI.createUI(c);
}
else if (key == "ViewportUI") {
return SynthViewportUI.createUI(c);
}
return null;
}
/**
* Creates a SynthLookAndFeel.
* SynthLookAndFeel
to be useful you need to
* invoke load
to specify the set of
* SynthStyle
s, or invoke setStyleFactory
.
*
* @see #load
* @see #setStyleFactory
*/
public SynthLookAndFeel() {
factory = new DefaultSynthStyleFactory();
_handler = new Handler();
}
/**
* Loads the set of SynthStyle
s that will be used by
* this SynthLookAndFeel
. resourceBase
is
* used to resolve any path based resources, for example an
* Image
would be resolved by
* resourceBase.getResource(path)
. Refer to
* Synth File Format
* for more information.
*
* @param input InputStream to load from
* @param resourceBase used to resolve any images or other resources
* @throws ParseException if there is an error in parsing
* @throws IllegalArgumentException if input or resourceBase is null
*/
public void load(InputStream input, Class> resourceBase) throws
ParseException {
if (resourceBase == null) {
throw new IllegalArgumentException(
"You must supply a valid resource base Class");
}
if (defaultsMap == null) {
defaultsMap = new HashMapSynthStyle
s that will be used by
* this SynthLookAndFeel
. Path based resources are resolved
* relatively to the specified URL
of the style. For example
* an Image
would be resolved by
* new URL(synthFile, path)
. Refer to
* Synth File Format for more
* information.
*
* @param url the URL
to load the set of
* SynthStyle
from
* @throws ParseException if there is an error in parsing
* @throws IllegalArgumentException if synthSet is null
* @throws IOException if synthSet cannot be opened as an InputStream
* @since 1.6
*/
public void load(URL url) throws ParseException, IOException {
if (url == null) {
throw new IllegalArgumentException(
"You must supply a valid Synth set URL");
}
if (defaultsMap == null) {
defaultsMap = new HashMapSynthStyles
from the SynthStyleFactory
* when the ancestor of the JComponent
changes. A subclass
* that provided a SynthStyleFactory
that based the
* return value from getStyle
off the containment hierarchy
* would override this method to return true.
*
* @return whether or not the UIs should update their
* SynthStyles
from the SynthStyleFactory
* when the ancestor changed.
*/
public boolean shouldUpdateStyleOnAncestorChanged() {
return false;
}
/**
* Returns whether or not the UIs should update their styles when a
* particular event occurs.
*
* @param ev a {@code PropertyChangeEvent}
* @return whether or not the UIs should update their styles
* @since 1.7
*/
protected boolean shouldUpdateStyleOnEvent(PropertyChangeEvent ev) {
String eName = ev.getPropertyName();
if ("name" == eName || "componentOrientation" == eName) {
return true;
}
if ("ancestor" == eName && ev.getNewValue() != null) {
// Only update on an ancestor change when getting a valid
// parent and the LookAndFeel wants this.
return shouldUpdateStyleOnAncestorChanged();
}
return false;
}
/**
* Returns the antialiasing information as specified by the host desktop.
* Antialiasing might be forced off if the desktop is GNOME and the user
* has set his locale to Chinese, Japanese or Korean. This is consistent
* with what GTK does. See com.sun.java.swing.plaf.gtk.GtkLookAndFeel
* for more information about CJK and antialiased fonts.
*
* @return the text antialiasing information associated to the desktop
*/
private static Object getAATextInfo() {
String language = Locale.getDefault().getLanguage();
String desktop =
AccessController.doPrivileged(new GetPropertyAction("sun.desktop"));
boolean isCjkLocale = (Locale.CHINESE.getLanguage().equals(language) ||
Locale.JAPANESE.getLanguage().equals(language) ||
Locale.KOREAN.getLanguage().equals(language));
boolean isGnome = "gnome".equals(desktop);
boolean isLocal = SwingUtilities2.isLocalDisplay();
boolean setAA = isLocal && (!isGnome || !isCjkLocale);
Object aaTextInfo = SwingUtilities2.AATextInfo.getAATextInfo(setAA);
return aaTextInfo;
}
private static ReferenceQueue