blob: 23c3f872fb6502bf2bcf052aba91409c82885c12 [file] [log] [blame]
/*
* Copyright (c) 2002, 2016, 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 com.sun.java.swing.plaf.gtk;
import java.awt.*;
import java.lang.reflect.*;
import java.security.*;
import java.util.*;
import javax.swing.*;
import javax.swing.plaf.*;
import javax.swing.plaf.synth.*;
import sun.awt.AppContext;
import sun.awt.UNIXToolkit;
import sun.swing.SwingUtilities2;
import javax.swing.plaf.synth.SynthIcon;
import com.sun.java.swing.plaf.gtk.GTKEngine.WidgetType;
import static java.awt.RenderingHints.KEY_TEXT_ANTIALIASING;
import static java.awt.RenderingHints.KEY_TEXT_LCD_CONTRAST;
/**
*
* @author Scott Violet
*/
class GTKStyle extends SynthStyle implements GTKConstants {
private static native int nativeGetXThickness(int widgetType);
private static native int nativeGetYThickness(int widgetType);
private static native int nativeGetColorForState(int widgetType,
int state, int typeID);
private static native Object nativeGetClassValue(int widgetType,
String key);
private static native String nativeGetPangoFontName(int widgetType);
private static final String ICON_PROPERTY_PREFIX = "gtk.icon.";
static final Color BLACK_COLOR = new ColorUIResource(Color.BLACK);
static final Color WHITE_COLOR = new ColorUIResource(Color.WHITE);
static final Font DEFAULT_FONT = new FontUIResource("sansserif",
Font.PLAIN, 10 );
static final Insets BUTTON_DEFAULT_BORDER_INSETS = new Insets(1, 1, 1, 1);
private static final GTKGraphicsUtils GTK_GRAPHICS = new GTKGraphicsUtils();
/**
* Maps from a key that is passed to Style.get to the equivalent class
* specific key.
*/
private static final Map<String,String> CLASS_SPECIFIC_MAP;
/**
* Backing style properties that are used if the style does not
* defined the property.
*/
private static final Map<String,GTKStockIcon> ICONS_MAP;
/**
* The font used for this particular style, as determined at
* construction time.
*/
private final Font font;
/** Widget type used when looking up class specific values. */
private final int widgetType;
/** The x/y thickness values for this particular style. */
private final int xThickness, yThickness;
GTKStyle(Font userFont, WidgetType widgetType) {
this.widgetType = widgetType.ordinal();
String pangoFontName;
synchronized (sun.awt.UNIXToolkit.GTK_LOCK) {
xThickness = nativeGetXThickness(this.widgetType);
yThickness = nativeGetYThickness(this.widgetType);
pangoFontName = nativeGetPangoFontName(this.widgetType);
}
Font pangoFont = null;
if (pangoFontName != null) {
pangoFont = PangoFonts.lookupFont(pangoFontName);
}
if (pangoFont != null) {
this.font = pangoFont;
} else if (userFont != null) {
this.font = userFont;
} else {
this.font = DEFAULT_FONT;
}
}
@Override
public void installDefaults(SynthContext context) {
super.installDefaults(context);
Map<Object, Object> aaTextInfo = GTKLookAndFeel.aaTextInfo;
if (aaTextInfo != null && !context.getRegion().isSubregion()) {
context.getComponent().putClientProperty(KEY_TEXT_ANTIALIASING,
aaTextInfo.get(KEY_TEXT_ANTIALIASING));
context.getComponent().putClientProperty(KEY_TEXT_LCD_CONTRAST,
aaTextInfo.get(KEY_TEXT_LCD_CONTRAST));
}
}
@Override
public SynthGraphicsUtils getGraphicsUtils(SynthContext context) {
return GTK_GRAPHICS;
}
/**
* Returns a <code>SynthPainter</code> that will route the appropriate
* calls to a <code>GTKEngine</code>.
*
* @param state SynthContext identifying requestor
* @return SynthPainter
*/
@Override
public SynthPainter getPainter(SynthContext state) {
return GTKPainter.INSTANCE;
}
protected Color getColorForState(SynthContext context, ColorType type) {
if (type == ColorType.FOCUS || type == GTKColorType.BLACK) {
return BLACK_COLOR;
}
else if (type == GTKColorType.WHITE) {
return WHITE_COLOR;
}
Region id = context.getRegion();
int state = context.getComponentState();
state = GTKLookAndFeel.synthStateToGTKState(id, state);
if (type == ColorType.TEXT_FOREGROUND &&
(id == Region.BUTTON ||
id == Region.CHECK_BOX ||
id == Region.CHECK_BOX_MENU_ITEM ||
id == Region.MENU ||
id == Region.MENU_ITEM ||
id == Region.RADIO_BUTTON ||
id == Region.RADIO_BUTTON_MENU_ITEM ||
id == Region.TABBED_PANE_TAB ||
id == Region.TOGGLE_BUTTON ||
id == Region.TOOL_TIP ||
id == Region.MENU_ITEM_ACCELERATOR ||
id == Region.TABBED_PANE_TAB)) {
type = ColorType.FOREGROUND;
} else if (id == Region.TABLE ||
id == Region.LIST ||
id == Region.TREE ||
id == Region.TREE_CELL) {
if (type == ColorType.FOREGROUND) {
type = ColorType.TEXT_FOREGROUND;
if (state == SynthConstants.PRESSED) {
state = SynthConstants.SELECTED;
}
} else if (type == ColorType.BACKGROUND) {
type = ColorType.TEXT_BACKGROUND;
}
}
return getStyleSpecificColor(context, state, type);
}
/**
* Returns color specific to the current style. This method is
* invoked when other variants don't fit.
*/
private Color getStyleSpecificColor(SynthContext context, int state,
ColorType type)
{
state = GTKLookAndFeel.synthStateToGTKStateType(state).ordinal();
synchronized (sun.awt.UNIXToolkit.GTK_LOCK) {
int rgb = nativeGetColorForState(widgetType, state,
type.getID());
return new ColorUIResource(rgb);
}
}
Color getGTKColor(int state, ColorType type) {
return getGTKColor(null, state, type);
}
/**
* Returns the color for the specified state.
*
* @param context SynthContext identifying requestor
* @param state to get the color for
* @param type of the color
* @return Color to render with
*/
Color getGTKColor(SynthContext context, int state, ColorType type) {
if (context != null) {
JComponent c = context.getComponent();
Region id = context.getRegion();
state = GTKLookAndFeel.synthStateToGTKState(id, state);
if (!id.isSubregion() &&
(state & SynthConstants.ENABLED) != 0) {
if (type == ColorType.BACKGROUND ||
type == ColorType.TEXT_BACKGROUND) {
Color bg = c.getBackground();
if (!(bg instanceof UIResource)) {
return bg;
}
}
else if (type == ColorType.FOREGROUND ||
type == ColorType.TEXT_FOREGROUND) {
Color fg = c.getForeground();
if (!(fg instanceof UIResource)) {
return fg;
}
}
}
}
return getStyleSpecificColor(context, state, type);
}
@Override
public Color getColor(SynthContext context, ColorType type) {
JComponent c = context.getComponent();
Region id = context.getRegion();
int state = context.getComponentState();
if (c.getName() == "Table.cellRenderer") {
if (type == ColorType.BACKGROUND) {
return c.getBackground();
}
if (type == ColorType.FOREGROUND) {
return c.getForeground();
}
}
if (id == Region.LABEL && type == ColorType.TEXT_FOREGROUND) {
type = ColorType.FOREGROUND;
}
// For the enabled state, prefer the widget's colors
if (!id.isSubregion() && (state & SynthConstants.ENABLED) != 0) {
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 != null && !(color instanceof UIResource)) {
return color;
}
}
}
return getColorForState(context, type);
}
Font getDefaultFont() {
return font;
}
protected Font getFontForState(SynthContext context) {
Font propFont = UIManager
.getFont(context.getRegion().getName() + ".font");
if (propFont != null) {
// if font property got a value then return it
return propFont;
}
return font;
}
/**
* Returns the X thickness to use for this GTKStyle.
*
* @return x thickness.
*/
int getXThickness() {
return xThickness;
}
/**
* Returns the Y thickness to use for this GTKStyle.
*
* @return y thickness.
*/
int getYThickness() {
return yThickness;
}
/**
* Returns the Insets. If <code>insets</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 identifying requestor
* @param insets Where to place Insets
* @return Insets.
*/
@Override
public Insets getInsets(SynthContext state, Insets insets) {
Region id = state.getRegion();
JComponent component = state.getComponent();
String name = (id.isSubregion()) ? null : component.getName();
if (insets == null) {
insets = new Insets(0, 0, 0, 0);
} else {
insets.top = insets.bottom = insets.left = insets.right = 0;
}
if (id == Region.ARROW_BUTTON || id == Region.BUTTON ||
id == Region.TOGGLE_BUTTON) {
if ("Spinner.previousButton" == name ||
"Spinner.nextButton" == name) {
return getSimpleInsets(state, insets, 1);
} else {
return getButtonInsets(state, insets);
}
}
else if (id == Region.CHECK_BOX || id == Region.RADIO_BUTTON) {
return getRadioInsets(state, insets);
}
else if (id == Region.MENU_BAR) {
return getMenuBarInsets(state, insets);
}
else if (id == Region.MENU ||
id == Region.MENU_ITEM ||
id == Region.CHECK_BOX_MENU_ITEM ||
id == Region.RADIO_BUTTON_MENU_ITEM) {
return getMenuItemInsets(state, insets);
}
else if (id == Region.FORMATTED_TEXT_FIELD) {
return getTextFieldInsets(state, insets);
}
else if (id == Region.INTERNAL_FRAME) {
insets = Metacity.INSTANCE.getBorderInsets(state, insets);
}
else if (id == Region.LABEL) {
if ("TableHeader.renderer" == name) {
return getButtonInsets(state, insets);
}
else if (component instanceof ListCellRenderer) {
return getTextFieldInsets(state, insets);
}
else if ("Tree.cellRenderer" == name) {
return getSimpleInsets(state, insets, 1);
}
}
else if (id == Region.OPTION_PANE) {
return getSimpleInsets(state, insets, 6);
}
else if (id == Region.POPUP_MENU) {
return getSimpleInsets(state, insets, 2);
}
else if (id == Region.PROGRESS_BAR || id == Region.SLIDER ||
id == Region.TABBED_PANE || id == Region.TABBED_PANE_CONTENT ||
id == Region.TOOL_BAR ||
id == Region.TOOL_BAR_DRAG_WINDOW ||
id == Region.TOOL_TIP) {
return getThicknessInsets(state, insets);
}
else if (id == Region.SCROLL_BAR) {
return getScrollBarInsets(state, insets);
}
else if (id == Region.SLIDER_TRACK) {
return getSliderTrackInsets(state, insets);
}
else if (id == Region.TABBED_PANE_TAB) {
return getTabbedPaneTabInsets(state, insets);
}
else if (id == Region.TEXT_FIELD || id == Region.PASSWORD_FIELD) {
if (name == "Tree.cellEditor") {
return getSimpleInsets(state, insets, 1);
}
return getTextFieldInsets(state, insets);
} else if (id == Region.SEPARATOR ||
id == Region.POPUP_MENU_SEPARATOR ||
id == Region.TOOL_BAR_SEPARATOR) {
return getSeparatorInsets(state, insets);
} else if (id == GTKEngine.CustomRegion.TITLED_BORDER) {
return getThicknessInsets(state, insets);
}
return insets;
}
private Insets getButtonInsets(SynthContext context, Insets insets) {
// The following calculations are derived from gtkbutton.c
// (GTK+ version 2.8.20), gtk_button_size_allocate() method.
int CHILD_SPACING = 1;
int focusSize = getClassSpecificIntValue(context, "focus-line-width",1);
int focusPad = getClassSpecificIntValue(context, "focus-padding", 1);
int xThickness = getXThickness();
int yThickness = getYThickness();
int w = focusSize + focusPad + xThickness + CHILD_SPACING;
int h = focusSize + focusPad + yThickness + CHILD_SPACING;
insets.left = insets.right = w;
insets.top = insets.bottom = h;
Component component = context.getComponent();
if ((component instanceof JButton) &&
!(component.getParent() instanceof JToolBar) &&
((JButton)component).isDefaultCapable())
{
// Include the default border insets, but only for JButtons
// that are default capable. Note that
// JButton.getDefaultCapable() returns true by default, but
// GtkToolButtons are never default capable, so we skip this
// step if the button is contained in a toolbar.
Insets defaultInsets = getClassSpecificInsetsValue(context,
"default-border", BUTTON_DEFAULT_BORDER_INSETS);
insets.left += defaultInsets.left;
insets.right += defaultInsets.right;
insets.top += defaultInsets.top;
insets.bottom += defaultInsets.bottom;
}
return insets;
}
/*
* This is used for both RADIO_BUTTON and CHECK_BOX.
*/
private Insets getRadioInsets(SynthContext context, Insets insets) {
// The following calculations are derived from gtkcheckbutton.c
// (GTK+ version 2.8.20), gtk_check_button_size_allocate() method.
int focusSize =
getClassSpecificIntValue(context, "focus-line-width", 1);
int focusPad =
getClassSpecificIntValue(context, "focus-padding", 1);
int totalFocus = focusSize + focusPad;
// Note: GTKIconFactory.DelegateIcon will have already included the
// "indicator-spacing" value in the size of the indicator icon,
// which explains why we use zero as the left inset (or right inset
// in the RTL case); see 6489585 for more details.
insets.top = totalFocus;
insets.bottom = totalFocus;
if (context.getComponent().getComponentOrientation().isLeftToRight()) {
insets.left = 0;
insets.right = totalFocus;
} else {
insets.left = totalFocus;
insets.right = 0;
}
return insets;
}
private Insets getMenuBarInsets(SynthContext context, Insets insets) {
// The following calculations are derived from gtkmenubar.c
// (GTK+ version 2.8.20), gtk_menu_bar_size_allocate() method.
int internalPadding = getClassSpecificIntValue(context,
"internal-padding", 1);
int xThickness = getXThickness();
int yThickness = getYThickness();
insets.left = insets.right = xThickness + internalPadding;
insets.top = insets.bottom = yThickness + internalPadding;
return insets;
}
private Insets getMenuItemInsets(SynthContext context, Insets insets) {
// The following calculations are derived from gtkmenuitem.c
// (GTK+ version 2.8.20), gtk_menu_item_size_allocate() method.
int horizPadding = getClassSpecificIntValue(context,
"horizontal-padding", 3);
int xThickness = getXThickness();
int yThickness = getYThickness();
insets.left = insets.right = xThickness + horizPadding;
insets.top = insets.bottom = yThickness;
return insets;
}
private Insets getThicknessInsets(SynthContext context, Insets insets) {
insets.left = insets.right = getXThickness();
insets.top = insets.bottom = getYThickness();
return insets;
}
private Insets getSeparatorInsets(SynthContext context, Insets insets) {
int horizPadding = 0;
if (context.getRegion() == Region.POPUP_MENU_SEPARATOR) {
horizPadding =
getClassSpecificIntValue(context, "horizontal-padding", 3);
}
insets.right = insets.left = getXThickness() + horizPadding;
insets.top = insets.bottom = getYThickness();
return insets;
}
private Insets getSliderTrackInsets(SynthContext context, Insets insets) {
int focusSize = getClassSpecificIntValue(context, "focus-line-width", 1);
int focusPad = getClassSpecificIntValue(context, "focus-padding", 1);
insets.top = insets.bottom =
insets.left = insets.right = focusSize + focusPad;
return insets;
}
private Insets getSimpleInsets(SynthContext context, Insets insets, int n) {
insets.top = insets.bottom = insets.right = insets.left = n;
return insets;
}
private Insets getTabbedPaneTabInsets(SynthContext context, Insets insets) {
int xThickness = getXThickness();
int yThickness = getYThickness();
int focusSize = getClassSpecificIntValue(context, "focus-line-width",1);
int pad = 2;
insets.left = insets.right = focusSize + pad + xThickness;
insets.top = insets.bottom = focusSize + pad + yThickness;
return insets;
}
// NOTE: this is called for ComboBox, and FormattedTextField also
private Insets getTextFieldInsets(SynthContext context, Insets insets) {
insets = getClassSpecificInsetsValue(context, "inner-border",
getSimpleInsets(context, insets, 2));
int xThickness = getXThickness();
int yThickness = getYThickness();
boolean interiorFocus =
getClassSpecificBoolValue(context, "interior-focus", true);
int focusSize = 0;
if (!interiorFocus) {
focusSize = getClassSpecificIntValue(context, "focus-line-width",1);
}
insets.left += focusSize + xThickness;
insets.right += focusSize + xThickness;
insets.top += focusSize + yThickness;
insets.bottom += focusSize + yThickness;
return insets;
}
private Insets getScrollBarInsets(SynthContext context, Insets insets) {
int troughBorder =
getClassSpecificIntValue(context, "trough-border", 1);
insets.left = insets.right = insets.top = insets.bottom = troughBorder;
JComponent c = context.getComponent();
if (c.getParent() instanceof JScrollPane) {
// This scrollbar is part of a scrollpane; use only the
// "scrollbar-spacing" style property to determine the padding
// between the scrollbar and its parent scrollpane.
int spacing =
getClassSpecificIntValue(WidgetType.SCROLL_PANE,
"scrollbar-spacing", 3);
if (((JScrollBar)c).getOrientation() == JScrollBar.HORIZONTAL) {
insets.top += spacing;
} else {
if (c.getComponentOrientation().isLeftToRight()) {
insets.left += spacing;
} else {
insets.right += spacing;
}
}
} else {
// This is a standalone scrollbar; leave enough room for the
// focus line in addition to the trough border.
if (c.isFocusable()) {
int focusSize =
getClassSpecificIntValue(context, "focus-line-width", 1);
int focusPad =
getClassSpecificIntValue(context, "focus-padding", 1);
int totalFocus = focusSize + focusPad;
insets.left += totalFocus;
insets.right += totalFocus;
insets.top += totalFocus;
insets.bottom += totalFocus;
}
}
return insets;
}
/**
* Returns the value for a class specific property for a particular
* WidgetType. This method is useful in those cases where we need to
* fetch a value for a Region that is not associated with the component
* currently in use (e.g. we need to figure out the insets for a
* SCROLL_BAR, but certain values can only be extracted from a
* SCROLL_PANE region).
*
* @param wt WidgetType for which to fetch the value
* @param key Key identifying class specific value
* @return Value, or null if one has not been defined
*/
private static Object getClassSpecificValue(WidgetType wt, String key) {
synchronized (UNIXToolkit.GTK_LOCK) {
return nativeGetClassValue(wt.ordinal(), key);
}
}
/**
* Convenience method to get a class specific integer value for
* a particular WidgetType.
*
* @param wt WidgetType for which to fetch the value
* @param key Key identifying class specific value
* @param defaultValue Returned if there is no value for the specified
* type
* @return Value, or defaultValue if <code>key</code> is not defined
*/
private static int getClassSpecificIntValue(WidgetType wt, String key,
int defaultValue)
{
Object value = getClassSpecificValue(wt, key);
if (value instanceof Number) {
return ((Number)value).intValue();
}
return defaultValue;
}
/**
* Returns the value for a class specific property. A class specific value
* is a value that will be picked up based on class hierarchy.
*
* @param key Key identifying class specific value
* @return Value, or null if one has not been defined.
*/
Object getClassSpecificValue(String key) {
synchronized (sun.awt.UNIXToolkit.GTK_LOCK) {
return nativeGetClassValue(widgetType, key);
}
}
/**
* Convenience method to get a class specific integer value.
*
* @param context SynthContext identifying requestor
* @param key Key identifying class specific value
* @param defaultValue Returned if there is no value for the specified
* type
* @return Value, or defaultValue if <code>key</code> is not defined
*/
int getClassSpecificIntValue(SynthContext context, String key,
int defaultValue)
{
Object value = getClassSpecificValue(key);
if (value instanceof Number) {
return ((Number)value).intValue();
}
return defaultValue;
}
/**
* Convenience method to get a class specific Insets value.
*
* @param context SynthContext identifying requestor
* @param key Key identifying class specific value
* @param defaultValue Returned if there is no value for the specified
* type
* @return Value, or defaultValue if <code>key</code> is not defined
*/
Insets getClassSpecificInsetsValue(SynthContext context, String key,
Insets defaultValue)
{
Object value = getClassSpecificValue(key);
if (value instanceof Insets) {
return (Insets)value;
}
return defaultValue;
}
/**
* Convenience method to get a class specific Boolean value.
*
* @param context SynthContext identifying requestor
* @param key Key identifying class specific value
* @param defaultValue Returned if there is no value for the specified
* type
* @return Value, or defaultValue if <code>key</code> is not defined
*/
boolean getClassSpecificBoolValue(SynthContext context, String key,
boolean defaultValue)
{
Object value = getClassSpecificValue(key);
if (value instanceof Boolean) {
return ((Boolean)value).booleanValue();
}
return defaultValue;
}
/**
* 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 context SynthContext identifying requestor
* @return opaque Whether or not the JComponent is opaque.
*/
@Override
public boolean isOpaque(SynthContext context) {
Region region = context.getRegion();
if (region == Region.COMBO_BOX ||
region == Region.DESKTOP_PANE ||
region == Region.DESKTOP_ICON ||
region == Region.INTERNAL_FRAME ||
region == Region.LIST ||
region == Region.MENU_BAR ||
region == Region.PANEL ||
region == Region.POPUP_MENU ||
region == Region.PROGRESS_BAR ||
region == Region.ROOT_PANE ||
region == Region.SCROLL_PANE ||
region == Region.SPLIT_PANE_DIVIDER ||
region == Region.TABLE ||
region == Region.TEXT_AREA ||
region == Region.TOOL_BAR_DRAG_WINDOW ||
region == Region.TOOL_TIP ||
region == Region.TREE ||
region == Region.VIEWPORT) {
return true;
}
if (!GTKLookAndFeel.is3()) {
if (region == Region.EDITOR_PANE ||
region == Region.FORMATTED_TEXT_FIELD ||
region == Region.PASSWORD_FIELD ||
region == Region.SPINNER ||
region == Region.TEXT_FIELD ||
region == Region.TEXT_PANE) {
return true;
}
}
Component c = context.getComponent();
String name = c.getName();
if (name == "ComboBox.renderer" || name == "ComboBox.listRenderer") {
return true;
}
return false;
}
@Override
public Object get(SynthContext context, Object key) {
// See if this is a class specific value.
String classKey = CLASS_SPECIFIC_MAP.get(key);
if (classKey != null) {
Object value = getClassSpecificValue(classKey);
if (value != null) {
return value;
}
}
// Is it a specific value ?
if (key == "ScrollPane.viewportBorderInsets") {
return getThicknessInsets(context, new Insets(0, 0, 0, 0));
}
else if (key == "Slider.tickColor") {
return getColorForState(context, ColorType.FOREGROUND);
}
else if (key == "ScrollBar.minimumThumbSize") {
int len =
getClassSpecificIntValue(context, "min-slider-length", 21);
JScrollBar sb = (JScrollBar)context.getComponent();
if (sb.getOrientation() == JScrollBar.HORIZONTAL) {
return new DimensionUIResource(len, 0);
} else {
return new DimensionUIResource(0, len);
}
}
else if (key == "Separator.thickness") {
JSeparator sep = (JSeparator)context.getComponent();
if (getClassSpecificBoolValue(context, "wide-separators", false)) {
if (sep.getOrientation() == JSeparator.HORIZONTAL) {
return getClassSpecificIntValue(context,
"separator-height", 0);
} else {
return getClassSpecificIntValue(context,
"separator-width", 0);
}
}
if (sep.getOrientation() == JSeparator.HORIZONTAL) {
return getYThickness();
} else {
return getXThickness();
}
}
else if (key == "ToolBar.separatorSize") {
if (getClassSpecificBoolValue(context, "wide-separators", false)) {
return new DimensionUIResource(
getClassSpecificIntValue(context, "separator-width", 2),
getClassSpecificIntValue(context, "separator-height", 2)
);
}
int size = getClassSpecificIntValue(WidgetType.TOOL_BAR,
"space-size", 12);
return new DimensionUIResource(size, size);
}
else if (key == "ScrollBar.buttonSize") {
JScrollBar sb = (JScrollBar)context.getComponent().getParent();
boolean horiz = (sb.getOrientation() == JScrollBar.HORIZONTAL);
WidgetType wt = horiz ?
WidgetType.HSCROLL_BAR : WidgetType.VSCROLL_BAR;
int sliderWidth = getClassSpecificIntValue(wt, "slider-width", 14);
int stepperSize = getClassSpecificIntValue(wt, "stepper-size", 14);
return horiz ?
new DimensionUIResource(stepperSize, sliderWidth) :
new DimensionUIResource(sliderWidth, stepperSize);
}
else if (key == "ArrowButton.size") {
String name = context.getComponent().getName();
if (name != null && name.startsWith("Spinner")) {
// Believe it or not, the size of a spinner arrow button is
// dependent upon the size of the spinner's font. These
// calculations come from gtkspinbutton.c (version 2.8.20),
// spin_button_get_arrow_size() method.
String pangoFontName;
synchronized (sun.awt.UNIXToolkit.GTK_LOCK) {
pangoFontName =
nativeGetPangoFontName(WidgetType.SPINNER.ordinal());
}
int arrowSize = (pangoFontName != null) ?
PangoFonts.getFontSize(pangoFontName) : 10;
return (arrowSize + (getXThickness() * 2));
}
// For all other kinds of arrow buttons (e.g. combobox arrow
// buttons), we will simply fall back on the value of
// ArrowButton.size as defined in the UIDefaults for
// GTKLookAndFeel when we call UIManager.get() below...
}
else if ("CheckBox.iconTextGap".equals(key) ||
"RadioButton.iconTextGap".equals(key))
{
// The iconTextGap value needs to include "indicator-spacing"
// and it also needs to leave enough space for the focus line,
// which falls between the indicator icon and the text.
// See getRadioInsets() and 6489585 for more details.
int indicatorSpacing =
getClassSpecificIntValue(context, "indicator-spacing", 2);
int focusSize =
getClassSpecificIntValue(context, "focus-line-width", 1);
int focusPad =
getClassSpecificIntValue(context, "focus-padding", 1);
return indicatorSpacing + focusSize + focusPad;
} else if (GTKLookAndFeel.is3() && "ComboBox.forceOpaque".equals(key)) {
return true;
} else if ("Tree.expanderSize".equals(key)) {
Object value = getClassSpecificValue("expander-size");
if (value instanceof Integer) {
return (Integer)value + 4;
}
return null;
}
// Is it a stock icon ?
GTKStockIcon stockIcon = null;
synchronized (ICONS_MAP) {
stockIcon = ICONS_MAP.get(key);
}
if (stockIcon != null) {
return stockIcon;
}
// Is it another kind of value ?
if (key != "engine") {
// For backward compatibility we'll fallback to the UIManager.
// We don't go to the UIManager for engine as the engine is GTK
// specific.
Object value = UIManager.get(key);
if (key == "Table.rowHeight") {
int focusLineWidth = getClassSpecificIntValue(context,
"focus-line-width", 0);
if (value == null && focusLineWidth > 0) {
value = Integer.valueOf(16 + 2 * focusLineWidth);
}
}
return value;
}
// Don't call super, we don't want to pick up defaults from
// SynthStyle.
return null;
}
private Icon getStockIcon(SynthContext context, String key, int type) {
TextDirection direction = TextDirection.LTR;
if (context != null) {
ComponentOrientation co = context.getComponent().
getComponentOrientation();
if (co != null && !co.isLeftToRight()) {
direction = TextDirection.RTL;
}
}
// First try loading a theme-specific icon using the native
// GTK libraries (native GTK handles the resizing for us).
Icon icon = getStyleSpecificIcon(key, direction, type);
if (icon != null) {
return icon;
}
// In a failure case where native GTK (unexpectedly) returns a
// null icon, we can try loading a default icon as a fallback.
String propName = ICON_PROPERTY_PREFIX + key + '.' + type + '.' +
(direction == TextDirection.RTL ? "rtl" : "ltr");
Image img = (Image)
Toolkit.getDefaultToolkit().getDesktopProperty(propName);
if (img != null) {
return new ImageIcon(img);
}
// In an extreme failure situation, just return null (callers are
// already prepared to handle a null icon, so the worst that can
// happen is that an icon won't be included in the button/dialog).
return null;
}
private Icon getStyleSpecificIcon(String key,
TextDirection direction, int type)
{
UNIXToolkit tk = (UNIXToolkit)Toolkit.getDefaultToolkit();
Image img =
tk.getStockIcon(widgetType, key, type, direction.ordinal(), null);
return (img != null) ? new ImageIcon(img) : null;
}
static class GTKStockIconInfo {
private static Map<String,Integer> ICON_TYPE_MAP;
private static final Object ICON_SIZE_KEY = new StringBuffer("IconSize");
private static Dimension[] getIconSizesMap() {
AppContext appContext = AppContext.getAppContext();
Dimension[] iconSizes = (Dimension[])appContext.get(ICON_SIZE_KEY);
if (iconSizes == null) {
iconSizes = new Dimension[7];
iconSizes[0] = null; // GTK_ICON_SIZE_INVALID
iconSizes[1] = new Dimension(16, 16); // GTK_ICON_SIZE_MENU
iconSizes[2] = new Dimension(18, 18); // GTK_ICON_SIZE_SMALL_TOOLBAR
iconSizes[3] = new Dimension(24, 24); // GTK_ICON_SIZE_LARGE_TOOLBAR
iconSizes[4] = new Dimension(20, 20); // GTK_ICON_SIZE_BUTTON
iconSizes[5] = new Dimension(32, 32); // GTK_ICON_SIZE_DND
iconSizes[6] = new Dimension(48, 48); // GTK_ICON_SIZE_DIALOG
appContext.put(ICON_SIZE_KEY, iconSizes);
}
return iconSizes;
}
/**
* Return the size of a particular icon type (logical size)
*
* @param type icon type (GtkIconSize value)
* @return a Dimension object, or null if lsize is invalid
*/
public static Dimension getIconSize(int type) {
Dimension[] iconSizes = getIconSizesMap();
return type >= 0 && type < iconSizes.length ?
iconSizes[type] : null;
}
/**
* Change icon size in a type to size mapping. This is called by code
* that parses the gtk-icon-sizes setting
*
* @param type icon type (GtkIconSize value)
* @param w the new icon width
* @param h the new icon height
*/
public static void setIconSize(int type, int w, int h) {
Dimension[] iconSizes = getIconSizesMap();
if (type >= 0 && type < iconSizes.length) {
iconSizes[type] = new Dimension(w, h);
}
}
/**
* Return icon type (GtkIconSize value) given a symbolic name which can
* occur in a theme file.
*
* @param size symbolic name, e.g. gtk-button
* @return icon type. Valid types are 1 to 6
*/
public static int getIconType(String size) {
if (size == null) {
return UNDEFINED;
}
if (ICON_TYPE_MAP == null) {
initIconTypeMap();
}
Integer n = ICON_TYPE_MAP.get(size);
return n != null ? n.intValue() : UNDEFINED;
}
private static void initIconTypeMap() {
ICON_TYPE_MAP = new HashMap<String,Integer>();
ICON_TYPE_MAP.put("gtk-menu", Integer.valueOf(1));
ICON_TYPE_MAP.put("gtk-small-toolbar", Integer.valueOf(2));
ICON_TYPE_MAP.put("gtk-large-toolbar", Integer.valueOf(3));
ICON_TYPE_MAP.put("gtk-button", Integer.valueOf(4));
ICON_TYPE_MAP.put("gtk-dnd", Integer.valueOf(5));
ICON_TYPE_MAP.put("gtk-dialog", Integer.valueOf(6));
}
}
/**
* An Icon that is fetched using getStockIcon.
*/
private static class GTKStockIcon implements SynthIcon {
private String key;
private int size;
private boolean loadedLTR;
private boolean loadedRTL;
private Icon ltrIcon;
private Icon rtlIcon;
private SynthStyle style;
GTKStockIcon(String key, int size) {
this.key = key;
this.size = size;
}
public void paintIcon(SynthContext context, Graphics g, int x,
int y, int w, int h) {
Icon icon = getIcon(context);
if (icon != null) {
if (context == null) {
icon.paintIcon(null, g, x, y);
}
else {
icon.paintIcon(context.getComponent(), g, x, y);
}
}
}
public int getIconWidth(SynthContext context) {
Icon icon = getIcon(context);
if (icon != null) {
return icon.getIconWidth();
}
return 0;
}
public int getIconHeight(SynthContext context) {
Icon icon = getIcon(context);
if (icon != null) {
return icon.getIconHeight();
}
return 0;
}
private Icon getIcon(SynthContext context) {
if (context != null) {
ComponentOrientation co = context.getComponent().
getComponentOrientation();
SynthStyle style = context.getStyle();
if (style != this.style) {
this.style = style;
loadedLTR = loadedRTL = false;
}
if (co == null || co.isLeftToRight()) {
if (!loadedLTR) {
loadedLTR = true;
ltrIcon = ((GTKStyle)context.getStyle()).
getStockIcon(context, key, size);
}
return ltrIcon;
}
else if (!loadedRTL) {
loadedRTL = true;
rtlIcon = ((GTKStyle)context.getStyle()).
getStockIcon(context, key,size);
}
return rtlIcon;
}
return ltrIcon;
}
}
/**
* GTKLazyValue is a slimmed down version of <code>ProxyLaxyValue</code>.
* The code is duplicate so that it can get at the package private
* classes in gtk.
*/
static class GTKLazyValue implements UIDefaults.LazyValue {
/**
* Name of the class to create.
*/
private String className;
private String methodName;
GTKLazyValue(String name) {
this(name, null);
}
GTKLazyValue(String name, String methodName) {
this.className = name;
this.methodName = methodName;
}
@SuppressWarnings("deprecation")
public Object createValue(UIDefaults table) {
try {
Class<?> c = Class.forName(className, true,Thread.currentThread().
getContextClassLoader());
if (methodName == null) {
return c.newInstance();
}
Method m = c.getMethod(methodName, (Class<?>[])null);
return m.invoke(c, (Object[])null);
} catch (ReflectiveOperationException e) {
}
return null;
}
}
static {
CLASS_SPECIFIC_MAP = new HashMap<String,String>();
CLASS_SPECIFIC_MAP.put("Slider.thumbHeight", "slider-width");
CLASS_SPECIFIC_MAP.put("Slider.thumbWidth", "slider-length");
CLASS_SPECIFIC_MAP.put("Slider.trackBorder", "trough-border");
CLASS_SPECIFIC_MAP.put("SplitPane.size", "handle-size");
CLASS_SPECIFIC_MAP.put("ScrollBar.thumbHeight", "slider-width");
CLASS_SPECIFIC_MAP.put("ScrollBar.width", "slider-width");
CLASS_SPECIFIC_MAP.put("TextArea.caretForeground", "cursor-color");
CLASS_SPECIFIC_MAP.put("TextArea.caretAspectRatio", "cursor-aspect-ratio");
CLASS_SPECIFIC_MAP.put("TextField.caretForeground", "cursor-color");
CLASS_SPECIFIC_MAP.put("TextField.caretAspectRatio", "cursor-aspect-ratio");
CLASS_SPECIFIC_MAP.put("PasswordField.caretForeground", "cursor-color");
CLASS_SPECIFIC_MAP.put("PasswordField.caretAspectRatio", "cursor-aspect-ratio");
CLASS_SPECIFIC_MAP.put("FormattedTextField.caretForeground", "cursor-color");
CLASS_SPECIFIC_MAP.put("FormattedTextField.caretAspectRatio", "cursor-aspect-");
CLASS_SPECIFIC_MAP.put("TextPane.caretForeground", "cursor-color");
CLASS_SPECIFIC_MAP.put("TextPane.caretAspectRatio", "cursor-aspect-ratio");
CLASS_SPECIFIC_MAP.put("EditorPane.caretForeground", "cursor-color");
CLASS_SPECIFIC_MAP.put("EditorPane.caretAspectRatio", "cursor-aspect-ratio");
ICONS_MAP = new HashMap<String, GTKStockIcon>();
ICONS_MAP.put("FileChooser.cancelIcon", new GTKStockIcon("gtk-cancel", 4));
ICONS_MAP.put("FileChooser.okIcon", new GTKStockIcon("gtk-ok", 4));
ICONS_MAP.put("OptionPane.errorIcon", new GTKStockIcon("gtk-dialog-error", 6));
ICONS_MAP.put("OptionPane.informationIcon", new GTKStockIcon("gtk-dialog-info", 6));
ICONS_MAP.put("OptionPane.warningIcon", new GTKStockIcon("gtk-dialog-warning", 6));
ICONS_MAP.put("OptionPane.questionIcon", new GTKStockIcon("gtk-dialog-question", 6));
ICONS_MAP.put("OptionPane.yesIcon", new GTKStockIcon("gtk-yes", 4));
ICONS_MAP.put("OptionPane.noIcon", new GTKStockIcon("gtk-no", 4));
ICONS_MAP.put("OptionPane.cancelIcon", new GTKStockIcon("gtk-cancel", 4));
ICONS_MAP.put("OptionPane.okIcon", new GTKStockIcon("gtk-ok", 4));
}
}