--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/com/sun/java/swing/plaf/gtk/GTKStyle.java Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,1141 @@
+/*
+ * Copyright 2002-2007 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.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 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 indentifying 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 requester
+ * @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 indentifying 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 indentifying 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 indentifying 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 indentifying 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 indentifying 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 compatability 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 = new Integer(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", new Integer(1));
+ ICON_TYPE_MAP.put("gtk-small-toolbar", new Integer(2));
+ ICON_TYPE_MAP.put("gtk-large-toolbar", new Integer(3));
+ ICON_TYPE_MAP.put("gtk-button", new Integer(4));
+ ICON_TYPE_MAP.put("gtk-dnd", new Integer(5));
+ ICON_TYPE_MAP.put("gtk-dialog", new Integer(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));
+ }
+}