/* * Copyright (c) 2002, 2018, 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 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 CLASS_SPECIFIC_MAP; /** * Backing style properties that are used if the style does not * defined the property. */ private static final Map 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 SynthPainter that will route the appropriate * calls to a GTKEngine. * * @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 insets 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 key 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 key 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 key 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 key 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 (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; } 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 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(); 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 ProxyLaxyValue. * 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(); 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(); 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)); } }