You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1142 lines
43 KiB
1142 lines
43 KiB
/*
|
|
* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
|
|
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 sun.swing.plaf.synth.SynthIcon;
|
|
|
|
import com.sun.java.swing.plaf.gtk.GTKEngine.WidgetType;
|
|
|
|
/**
|
|
*
|
|
* @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);
|
|
if (!context.getRegion().isSubregion()) {
|
|
context.getComponent().putClientProperty(
|
|
SwingUtilities2.AA_TEXT_PROPERTY_KEY,
|
|
GTKLookAndFeel.aaTextInfo);
|
|
}
|
|
}
|
|
|
|
@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);
|
|
}
|
|
|
|
protected Font getFontForState(SynthContext context) {
|
|
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.EDITOR_PANE ||
|
|
region == Region.FORMATTED_TEXT_FIELD ||
|
|
region == Region.INTERNAL_FRAME ||
|
|
region == Region.LIST ||
|
|
region == Region.MENU_BAR ||
|
|
region == Region.PANEL ||
|
|
region == Region.PASSWORD_FIELD ||
|
|
region == Region.POPUP_MENU ||
|
|
region == Region.PROGRESS_BAR ||
|
|
region == Region.ROOT_PANE ||
|
|
region == Region.SCROLL_PANE ||
|
|
region == Region.SPINNER ||
|
|
region == Region.SPLIT_PANE_DIVIDER ||
|
|
region == Region.TABLE ||
|
|
region == Region.TEXT_AREA ||
|
|
region == Region.TEXT_FIELD ||
|
|
region == Region.TEXT_PANE ||
|
|
region == Region.TOOL_BAR_DRAG_WINDOW ||
|
|
region == Region.TOOL_TIP ||
|
|
region == Region.TREE ||
|
|
region == Region.VIEWPORT) {
|
|
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 (sep.getOrientation() == JSeparator.HORIZONTAL) {
|
|
return getYThickness();
|
|
} else {
|
|
return getXThickness();
|
|
}
|
|
}
|
|
else if (key == "ToolBar.separatorSize") {
|
|
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;
|
|
}
|
|
|
|
// 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 extends 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;
|
|
}
|
|
|
|
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 (ClassNotFoundException cnfe) {
|
|
} catch (IllegalAccessException iae) {
|
|
} catch (InvocationTargetException ite) {
|
|
} catch (NoSuchMethodException nsme) {
|
|
} catch (InstantiationException ie) {
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
static {
|
|
CLASS_SPECIFIC_MAP = new HashMap<String,String>();
|
|
CLASS_SPECIFIC_MAP.put("Slider.thumbHeight", "slider-width");
|
|
CLASS_SPECIFIC_MAP.put("Slider.trackBorder", "trough-border");
|
|
CLASS_SPECIFIC_MAP.put("SplitPane.size", "handle-size");
|
|
CLASS_SPECIFIC_MAP.put("Tree.expanderSize", "expander-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));
|
|
}
|
|
}
|