1 /*
   2  * Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.java.swing.plaf.gtk;
  27 
  28 import java.awt.*;
  29 import java.lang.reflect.*;
  30 import java.security.*;
  31 import java.util.*;
  32 import javax.swing.*;
  33 import javax.swing.plaf.*;
  34 import javax.swing.plaf.synth.*;
  35 
  36 import sun.awt.AppContext;
  37 import sun.awt.UNIXToolkit;
  38 import sun.swing.SwingUtilities2;
  39 import sun.swing.plaf.synth.SynthIcon;
  40 
  41 import com.sun.java.swing.plaf.gtk.GTKEngine.WidgetType;
  42 
  43 /**
  44  *
  45  * @author Scott Violet
  46  */
  47 class GTKStyle extends SynthStyle implements GTKConstants {
  48 
  49     private static native int nativeGetXThickness(int widgetType);
  50     private static native int nativeGetYThickness(int widgetType);
  51     private static native int nativeGetColorForState(int widgetType,
  52                                                      int state, int typeID);
  53     private static native Object nativeGetClassValue(int widgetType,
  54                                                      String key);
  55     private static native String nativeGetPangoFontName(int widgetType);
  56 
  57     private static final String ICON_PROPERTY_PREFIX = "gtk.icon.";
  58 
  59     static final Color BLACK_COLOR = new ColorUIResource(Color.BLACK);
  60     static final Color WHITE_COLOR = new ColorUIResource(Color.WHITE);
  61 
  62     static final Font DEFAULT_FONT = new FontUIResource("sansserif",
  63                                                         Font.PLAIN, 10  );
  64     static final Insets BUTTON_DEFAULT_BORDER_INSETS = new Insets(1, 1, 1, 1);
  65 
  66     private static final GTKGraphicsUtils GTK_GRAPHICS = new GTKGraphicsUtils();
  67 
  68     /**
  69      * Maps from a key that is passed to Style.get to the equivalent class
  70      * specific key.
  71      */
  72     private static final Map<String,String> CLASS_SPECIFIC_MAP;
  73 
  74     /**
  75      * Backing style properties that are used if the style does not
  76      * defined the property.
  77      */
  78     private static final Map<String,GTKStockIcon> ICONS_MAP;
  79 
  80     /**
  81      * The font used for this particular style, as determined at
  82      * construction time.
  83      */
  84     private final Font font;
  85 
  86     /** Widget type used when looking up class specific values. */
  87     private final int widgetType;
  88 
  89     /** The x/y thickness values for this particular style. */
  90     private final int xThickness, yThickness;
  91 
  92     GTKStyle(Font userFont, WidgetType widgetType) {
  93         this.widgetType = widgetType.ordinal();
  94 
  95         String pangoFontName;
  96         synchronized (sun.awt.UNIXToolkit.GTK_LOCK) {
  97             xThickness = nativeGetXThickness(this.widgetType);
  98             yThickness = nativeGetYThickness(this.widgetType);
  99             pangoFontName = nativeGetPangoFontName(this.widgetType);
 100         }
 101 
 102         Font pangoFont = null;
 103         if (pangoFontName != null) {
 104             pangoFont = PangoFonts.lookupFont(pangoFontName);
 105         }
 106         if (pangoFont != null) {
 107             this.font = pangoFont;
 108         } else if (userFont != null) {
 109             this.font = userFont;
 110         } else {
 111             this.font = DEFAULT_FONT;
 112         }
 113     }
 114 
 115     @Override
 116     public void installDefaults(SynthContext context) {
 117         super.installDefaults(context);
 118         if (!context.getRegion().isSubregion()) {
 119             context.getComponent().putClientProperty(
 120                 SwingUtilities2.AA_TEXT_PROPERTY_KEY,
 121                 GTKLookAndFeel.aaTextInfo);
 122         }
 123     }
 124 
 125     @Override
 126     public SynthGraphicsUtils getGraphicsUtils(SynthContext context) {
 127         return GTK_GRAPHICS;
 128     }
 129 
 130     /**
 131      * Returns a <code>SynthPainter</code> that will route the appropriate
 132      * calls to a <code>GTKEngine</code>.
 133      *
 134      * @param state SynthContext identifying requestor
 135      * @return SynthPainter
 136      */
 137     @Override
 138     public SynthPainter getPainter(SynthContext state) {
 139         return GTKPainter.INSTANCE;
 140     }
 141 
 142     protected Color getColorForState(SynthContext context, ColorType type) {
 143         if (type == ColorType.FOCUS || type == GTKColorType.BLACK) {
 144             return BLACK_COLOR;
 145         }
 146         else if (type == GTKColorType.WHITE) {
 147             return WHITE_COLOR;
 148         }
 149 
 150         Region id = context.getRegion();
 151         int state = context.getComponentState();
 152         state = GTKLookAndFeel.synthStateToGTKState(id, state);
 153 
 154         if (type == ColorType.TEXT_FOREGROUND &&
 155                (id == Region.BUTTON ||
 156                 id == Region.CHECK_BOX ||
 157                 id == Region.CHECK_BOX_MENU_ITEM ||
 158                 id == Region.MENU ||
 159                 id == Region.MENU_ITEM ||
 160                 id == Region.RADIO_BUTTON ||
 161                 id == Region.RADIO_BUTTON_MENU_ITEM ||
 162                 id == Region.TABBED_PANE_TAB ||
 163                 id == Region.TOGGLE_BUTTON ||
 164                 id == Region.TOOL_TIP ||
 165                 id == Region.MENU_ITEM_ACCELERATOR ||
 166                 id == Region.TABBED_PANE_TAB)) {
 167             type = ColorType.FOREGROUND;
 168         } else if (id == Region.TABLE ||
 169                    id == Region.LIST ||
 170                    id == Region.TREE ||
 171                    id == Region.TREE_CELL) {
 172             if (type == ColorType.FOREGROUND) {
 173                 type = ColorType.TEXT_FOREGROUND;
 174                 if (state == SynthConstants.PRESSED) {
 175                     state = SynthConstants.SELECTED;
 176                 }
 177             } else if (type == ColorType.BACKGROUND) {
 178                 type = ColorType.TEXT_BACKGROUND;
 179             }
 180         }
 181 
 182         return getStyleSpecificColor(context, state, type);
 183     }
 184 
 185     /**
 186      * Returns color specific to the current style. This method is
 187      * invoked when other variants don't fit.
 188      */
 189     private Color getStyleSpecificColor(SynthContext context, int state,
 190                                         ColorType type)
 191     {
 192         state = GTKLookAndFeel.synthStateToGTKStateType(state).ordinal();
 193         synchronized (sun.awt.UNIXToolkit.GTK_LOCK) {
 194             int rgb = nativeGetColorForState(widgetType, state,
 195                                              type.getID());
 196             return new ColorUIResource(rgb);
 197         }
 198     }
 199 
 200     Color getGTKColor(int state, ColorType type) {
 201         return getGTKColor(null, state, type);
 202     }
 203 
 204     /**
 205      * Returns the color for the specified state.
 206      *
 207      * @param context SynthContext identifying requestor
 208      * @param state to get the color for
 209      * @param type of the color
 210      * @return Color to render with
 211      */
 212     Color getGTKColor(SynthContext context, int state, ColorType type) {
 213         if (context != null) {
 214             JComponent c = context.getComponent();
 215             Region id = context.getRegion();
 216 
 217             state = GTKLookAndFeel.synthStateToGTKState(id, state);
 218             if (!id.isSubregion() &&
 219                 (state & SynthConstants.ENABLED) != 0) {
 220                 if (type == ColorType.BACKGROUND ||
 221                     type == ColorType.TEXT_BACKGROUND) {
 222                     Color bg = c.getBackground();
 223                     if (!(bg instanceof UIResource)) {
 224                         return bg;
 225                     }
 226                 }
 227                 else if (type == ColorType.FOREGROUND ||
 228                          type == ColorType.TEXT_FOREGROUND) {
 229                     Color fg = c.getForeground();
 230                     if (!(fg instanceof UIResource)) {
 231                         return fg;
 232                     }
 233                 }
 234             }
 235         }
 236 
 237         return getStyleSpecificColor(context, state, type);
 238     }
 239 
 240     @Override
 241     public Color getColor(SynthContext context, ColorType type) {
 242         JComponent c = context.getComponent();
 243         Region id = context.getRegion();
 244         int state = context.getComponentState();
 245 
 246         if (c.getName() == "Table.cellRenderer") {
 247              if (type == ColorType.BACKGROUND) {
 248                  return c.getBackground();
 249              }
 250              if (type == ColorType.FOREGROUND) {
 251                  return c.getForeground();
 252              }
 253         }
 254 
 255         if (id == Region.LABEL && type == ColorType.TEXT_FOREGROUND) {
 256             type = ColorType.FOREGROUND;
 257         }
 258 
 259         // For the enabled state, prefer the widget's colors
 260         if (!id.isSubregion() && (state & SynthConstants.ENABLED) != 0) {
 261             if (type == ColorType.BACKGROUND) {
 262                 return c.getBackground();
 263             }
 264             else if (type == ColorType.FOREGROUND) {
 265                 return c.getForeground();
 266             }
 267             else if (type == ColorType.TEXT_FOREGROUND) {
 268                 // If getForeground returns a non-UIResource it means the
 269                 // developer has explicitly set the foreground, use it over
 270                 // that of TEXT_FOREGROUND as that is typically the expected
 271                 // behavior.
 272                 Color color = c.getForeground();
 273                 if (color != null && !(color instanceof UIResource)) {
 274                     return color;
 275                 }
 276             }
 277         }
 278         return getColorForState(context, type);
 279     }
 280 
 281     Font getDefaultFont() {
 282         return font;
 283     }
 284 
 285     protected Font getFontForState(SynthContext context) {
 286         Font propFont = UIManager
 287                               .getFont(context.getRegion().getName() + ".font");
 288         if (propFont != null) {
 289             // if font property got a value then return it
 290             return propFont;
 291         }
 292         return font;
 293     }
 294 
 295     /**
 296      * Returns the X thickness to use for this GTKStyle.
 297      *
 298      * @return x thickness.
 299      */
 300     int getXThickness() {
 301         return xThickness;
 302     }
 303 
 304     /**
 305      * Returns the Y thickness to use for this GTKStyle.
 306      *
 307      * @return y thickness.
 308      */
 309     int getYThickness() {
 310         return yThickness;
 311     }
 312 
 313     /**
 314      * Returns the Insets. If <code>insets</code> is non-null the resulting
 315      * insets will be placed in it, otherwise a new Insets object will be
 316      * created and returned.
 317      *
 318      * @param context SynthContext identifying requestor
 319      * @param insets Where to place Insets
 320      * @return Insets.
 321      */
 322     @Override
 323     public Insets getInsets(SynthContext state, Insets insets) {
 324         Region id = state.getRegion();
 325         JComponent component = state.getComponent();
 326         String name = (id.isSubregion()) ? null : component.getName();
 327 
 328         if (insets == null) {
 329             insets = new Insets(0, 0, 0, 0);
 330         } else {
 331             insets.top = insets.bottom = insets.left = insets.right = 0;
 332         }
 333 
 334         if (id == Region.ARROW_BUTTON || id == Region.BUTTON ||
 335                 id == Region.TOGGLE_BUTTON) {
 336             if ("Spinner.previousButton" == name ||
 337                     "Spinner.nextButton" == name) {
 338                 return getSimpleInsets(state, insets, 1);
 339             } else {
 340                 return getButtonInsets(state, insets);
 341             }
 342         }
 343         else if (id == Region.CHECK_BOX || id == Region.RADIO_BUTTON) {
 344             return getRadioInsets(state, insets);
 345         }
 346         else if (id == Region.MENU_BAR) {
 347             return getMenuBarInsets(state, insets);
 348         }
 349         else if (id == Region.MENU ||
 350                  id == Region.MENU_ITEM ||
 351                  id == Region.CHECK_BOX_MENU_ITEM ||
 352                  id == Region.RADIO_BUTTON_MENU_ITEM) {
 353             return getMenuItemInsets(state, insets);
 354         }
 355         else if (id == Region.FORMATTED_TEXT_FIELD) {
 356             return getTextFieldInsets(state, insets);
 357         }
 358         else if (id == Region.INTERNAL_FRAME) {
 359             insets = Metacity.INSTANCE.getBorderInsets(state, insets);
 360         }
 361         else if (id == Region.LABEL) {
 362             if ("TableHeader.renderer" == name) {
 363                 return getButtonInsets(state, insets);
 364             }
 365             else if (component instanceof ListCellRenderer) {
 366                 return getTextFieldInsets(state, insets);
 367             }
 368             else if ("Tree.cellRenderer" == name) {
 369                 return getSimpleInsets(state, insets, 1);
 370             }
 371         }
 372         else if (id == Region.OPTION_PANE) {
 373             return getSimpleInsets(state, insets, 6);
 374         }
 375         else if (id == Region.POPUP_MENU) {
 376             return getSimpleInsets(state, insets, 2);
 377         }
 378         else if (id == Region.PROGRESS_BAR || id == Region.SLIDER ||
 379                  id == Region.TABBED_PANE  || id == Region.TABBED_PANE_CONTENT ||
 380                  id == Region.TOOL_BAR     ||
 381                  id == Region.TOOL_BAR_DRAG_WINDOW ||
 382                  id == Region.TOOL_TIP) {
 383             return getThicknessInsets(state, insets);
 384         }
 385         else if (id == Region.SCROLL_BAR) {
 386             return getScrollBarInsets(state, insets);
 387         }
 388         else if (id == Region.SLIDER_TRACK) {
 389             return getSliderTrackInsets(state, insets);
 390         }
 391         else if (id == Region.TABBED_PANE_TAB) {
 392             return getTabbedPaneTabInsets(state, insets);
 393         }
 394         else if (id == Region.TEXT_FIELD || id == Region.PASSWORD_FIELD) {
 395             if (name == "Tree.cellEditor") {
 396                 return getSimpleInsets(state, insets, 1);
 397             }
 398             return getTextFieldInsets(state, insets);
 399         } else if (id == Region.SEPARATOR ||
 400                    id == Region.POPUP_MENU_SEPARATOR ||
 401                    id == Region.TOOL_BAR_SEPARATOR) {
 402             return getSeparatorInsets(state, insets);
 403         } else if (id == GTKEngine.CustomRegion.TITLED_BORDER) {
 404             return getThicknessInsets(state, insets);
 405         }
 406         return insets;
 407     }
 408 
 409     private Insets getButtonInsets(SynthContext context, Insets insets) {
 410         // The following calculations are derived from gtkbutton.c
 411         // (GTK+ version 2.8.20), gtk_button_size_allocate() method.
 412         int CHILD_SPACING = 1;
 413         int focusSize = getClassSpecificIntValue(context, "focus-line-width",1);
 414         int focusPad = getClassSpecificIntValue(context, "focus-padding", 1);
 415         int xThickness = getXThickness();
 416         int yThickness = getYThickness();
 417         int w = focusSize + focusPad + xThickness + CHILD_SPACING;
 418         int h = focusSize + focusPad + yThickness + CHILD_SPACING;
 419         insets.left = insets.right = w;
 420         insets.top = insets.bottom = h;
 421 
 422         Component component = context.getComponent();
 423         if ((component instanceof JButton) &&
 424             !(component.getParent() instanceof JToolBar) &&
 425             ((JButton)component).isDefaultCapable())
 426         {
 427             // Include the default border insets, but only for JButtons
 428             // that are default capable.  Note that
 429             // JButton.getDefaultCapable() returns true by default, but
 430             // GtkToolButtons are never default capable, so we skip this
 431             // step if the button is contained in a toolbar.
 432             Insets defaultInsets = getClassSpecificInsetsValue(context,
 433                           "default-border", BUTTON_DEFAULT_BORDER_INSETS);
 434             insets.left += defaultInsets.left;
 435             insets.right += defaultInsets.right;
 436             insets.top += defaultInsets.top;
 437             insets.bottom += defaultInsets.bottom;
 438         }
 439 
 440         return insets;
 441     }
 442 
 443     /*
 444      * This is used for both RADIO_BUTTON and CHECK_BOX.
 445      */
 446     private Insets getRadioInsets(SynthContext context, Insets insets) {
 447         // The following calculations are derived from gtkcheckbutton.c
 448         // (GTK+ version 2.8.20), gtk_check_button_size_allocate() method.
 449         int focusSize =
 450             getClassSpecificIntValue(context, "focus-line-width", 1);
 451         int focusPad =
 452             getClassSpecificIntValue(context, "focus-padding", 1);
 453         int totalFocus = focusSize + focusPad;
 454 
 455         // Note: GTKIconFactory.DelegateIcon will have already included the
 456         // "indicator-spacing" value in the size of the indicator icon,
 457         // which explains why we use zero as the left inset (or right inset
 458         // in the RTL case); see 6489585 for more details.
 459         insets.top    = totalFocus;
 460         insets.bottom = totalFocus;
 461         if (context.getComponent().getComponentOrientation().isLeftToRight()) {
 462             insets.left  = 0;
 463             insets.right = totalFocus;
 464         } else {
 465             insets.left  = totalFocus;
 466             insets.right = 0;
 467         }
 468 
 469         return insets;
 470     }
 471 
 472     private Insets getMenuBarInsets(SynthContext context, Insets insets) {
 473         // The following calculations are derived from gtkmenubar.c
 474         // (GTK+ version 2.8.20), gtk_menu_bar_size_allocate() method.
 475         int internalPadding = getClassSpecificIntValue(context,
 476                                                        "internal-padding", 1);
 477         int xThickness = getXThickness();
 478         int yThickness = getYThickness();
 479         insets.left = insets.right = xThickness + internalPadding;
 480         insets.top = insets.bottom = yThickness + internalPadding;
 481         return insets;
 482     }
 483 
 484     private Insets getMenuItemInsets(SynthContext context, Insets insets) {
 485         // The following calculations are derived from gtkmenuitem.c
 486         // (GTK+ version 2.8.20), gtk_menu_item_size_allocate() method.
 487         int horizPadding = getClassSpecificIntValue(context,
 488                                                     "horizontal-padding", 3);
 489         int xThickness = getXThickness();
 490         int yThickness = getYThickness();
 491         insets.left = insets.right = xThickness + horizPadding;
 492         insets.top = insets.bottom = yThickness;
 493         return insets;
 494     }
 495 
 496     private Insets getThicknessInsets(SynthContext context, Insets insets) {
 497         insets.left = insets.right = getXThickness();
 498         insets.top = insets.bottom = getYThickness();
 499         return insets;
 500     }
 501 
 502     private Insets getSeparatorInsets(SynthContext context, Insets insets) {
 503         int horizPadding = 0;
 504         if (context.getRegion() == Region.POPUP_MENU_SEPARATOR) {
 505             horizPadding =
 506                 getClassSpecificIntValue(context, "horizontal-padding", 3);
 507         }
 508         insets.right = insets.left = getXThickness() + horizPadding;
 509         insets.top = insets.bottom = getYThickness();
 510         return insets;
 511     }
 512 
 513     private Insets getSliderTrackInsets(SynthContext context, Insets insets) {
 514         int focusSize = getClassSpecificIntValue(context, "focus-line-width", 1);
 515         int focusPad = getClassSpecificIntValue(context, "focus-padding", 1);
 516         insets.top = insets.bottom =
 517                 insets.left = insets.right = focusSize + focusPad;
 518         return insets;
 519     }
 520 
 521     private Insets getSimpleInsets(SynthContext context, Insets insets, int n) {
 522         insets.top = insets.bottom = insets.right = insets.left = n;
 523         return insets;
 524     }
 525 
 526     private Insets getTabbedPaneTabInsets(SynthContext context, Insets insets) {
 527         int xThickness = getXThickness();
 528         int yThickness = getYThickness();
 529         int focusSize = getClassSpecificIntValue(context, "focus-line-width",1);
 530         int pad = 2;
 531 
 532         insets.left = insets.right = focusSize + pad + xThickness;
 533         insets.top = insets.bottom = focusSize + pad + yThickness;
 534         return insets;
 535     }
 536 
 537     // NOTE: this is called for ComboBox, and FormattedTextField also
 538     private Insets getTextFieldInsets(SynthContext context, Insets insets) {
 539         insets = getClassSpecificInsetsValue(context, "inner-border",
 540                                     getSimpleInsets(context, insets, 2));
 541 
 542         int xThickness = getXThickness();
 543         int yThickness = getYThickness();
 544         boolean interiorFocus =
 545                 getClassSpecificBoolValue(context, "interior-focus", true);
 546         int focusSize = 0;
 547 
 548         if (!interiorFocus) {
 549             focusSize = getClassSpecificIntValue(context, "focus-line-width",1);
 550         }
 551 
 552         insets.left   += focusSize + xThickness;
 553         insets.right  += focusSize + xThickness;
 554         insets.top    += focusSize + yThickness;
 555         insets.bottom += focusSize + yThickness;
 556         return insets;
 557     }
 558 
 559     private Insets getScrollBarInsets(SynthContext context, Insets insets) {
 560         int troughBorder =
 561             getClassSpecificIntValue(context, "trough-border", 1);
 562         insets.left = insets.right = insets.top = insets.bottom = troughBorder;
 563 
 564         JComponent c = context.getComponent();
 565         if (c.getParent() instanceof JScrollPane) {
 566             // This scrollbar is part of a scrollpane; use only the
 567             // "scrollbar-spacing" style property to determine the padding
 568             // between the scrollbar and its parent scrollpane.
 569             int spacing =
 570                 getClassSpecificIntValue(WidgetType.SCROLL_PANE,
 571                                          "scrollbar-spacing", 3);
 572             if (((JScrollBar)c).getOrientation() == JScrollBar.HORIZONTAL) {
 573                 insets.top += spacing;
 574             } else {
 575                 if (c.getComponentOrientation().isLeftToRight()) {
 576                     insets.left += spacing;
 577                 } else {
 578                     insets.right += spacing;
 579                 }
 580             }
 581         } else {
 582             // This is a standalone scrollbar; leave enough room for the
 583             // focus line in addition to the trough border.
 584             if (c.isFocusable()) {
 585                 int focusSize =
 586                     getClassSpecificIntValue(context, "focus-line-width", 1);
 587                 int focusPad =
 588                     getClassSpecificIntValue(context, "focus-padding", 1);
 589                 int totalFocus = focusSize + focusPad;
 590                 insets.left   += totalFocus;
 591                 insets.right  += totalFocus;
 592                 insets.top    += totalFocus;
 593                 insets.bottom += totalFocus;
 594             }
 595         }
 596         return insets;
 597     }
 598 
 599     /**
 600      * Returns the value for a class specific property for a particular
 601      * WidgetType.  This method is useful in those cases where we need to
 602      * fetch a value for a Region that is not associated with the component
 603      * currently in use (e.g. we need to figure out the insets for a
 604      * SCROLL_BAR, but certain values can only be extracted from a
 605      * SCROLL_PANE region).
 606      *
 607      * @param wt WidgetType for which to fetch the value
 608      * @param key Key identifying class specific value
 609      * @return Value, or null if one has not been defined
 610      */
 611     private static Object getClassSpecificValue(WidgetType wt, String key) {
 612         synchronized (UNIXToolkit.GTK_LOCK) {
 613             return nativeGetClassValue(wt.ordinal(), key);
 614         }
 615     }
 616 
 617     /**
 618      * Convenience method to get a class specific integer value for
 619      * a particular WidgetType.
 620      *
 621      * @param wt WidgetType for which to fetch the value
 622      * @param key Key identifying class specific value
 623      * @param defaultValue Returned if there is no value for the specified
 624      *        type
 625      * @return Value, or defaultValue if <code>key</code> is not defined
 626      */
 627     private static int getClassSpecificIntValue(WidgetType wt, String key,
 628                                                 int defaultValue)
 629     {
 630         Object value = getClassSpecificValue(wt, key);
 631         if (value instanceof Number) {
 632             return ((Number)value).intValue();
 633         }
 634         return defaultValue;
 635     }
 636 
 637     /**
 638      * Returns the value for a class specific property. A class specific value
 639      * is a value that will be picked up based on class hierarchy.
 640      *
 641      * @param key Key identifying class specific value
 642      * @return Value, or null if one has not been defined.
 643      */
 644     Object getClassSpecificValue(String key) {
 645         synchronized (sun.awt.UNIXToolkit.GTK_LOCK) {
 646             return nativeGetClassValue(widgetType, key);
 647         }
 648     }
 649 
 650     /**
 651      * Convenience method to get a class specific integer value.
 652      *
 653      * @param context SynthContext identifying requestor
 654      * @param key Key identifying class specific value
 655      * @param defaultValue Returned if there is no value for the specified
 656      *        type
 657      * @return Value, or defaultValue if <code>key</code> is not defined
 658      */
 659     int getClassSpecificIntValue(SynthContext context, String key,
 660                                  int defaultValue)
 661     {
 662         Object value = getClassSpecificValue(key);
 663 
 664         if (value instanceof Number) {
 665             return ((Number)value).intValue();
 666         }
 667         return defaultValue;
 668     }
 669 
 670     /**
 671      * Convenience method to get a class specific Insets value.
 672      *
 673      * @param context SynthContext identifying requestor
 674      * @param key Key identifying class specific value
 675      * @param defaultValue Returned if there is no value for the specified
 676      *        type
 677      * @return Value, or defaultValue if <code>key</code> is not defined
 678      */
 679     Insets getClassSpecificInsetsValue(SynthContext context, String key,
 680                                        Insets defaultValue)
 681     {
 682         Object value = getClassSpecificValue(key);
 683 
 684         if (value instanceof Insets) {
 685             return (Insets)value;
 686         }
 687         return defaultValue;
 688     }
 689 
 690     /**
 691      * Convenience method to get a class specific Boolean value.
 692      *
 693      * @param context SynthContext identifying requestor
 694      * @param key Key identifying class specific value
 695      * @param defaultValue Returned if there is no value for the specified
 696      *        type
 697      * @return Value, or defaultValue if <code>key</code> is not defined
 698      */
 699     boolean getClassSpecificBoolValue(SynthContext context, String key,
 700                                       boolean defaultValue)
 701     {
 702         Object value = getClassSpecificValue(key);
 703 
 704         if (value instanceof Boolean) {
 705             return ((Boolean)value).booleanValue();
 706         }
 707         return defaultValue;
 708     }
 709 
 710     /**
 711      * Returns the value to initialize the opacity property of the Component
 712      * to. A Style should NOT assume the opacity will remain this value, the
 713      * developer may reset it or override it.
 714      *
 715      * @param context SynthContext identifying requestor
 716      * @return opaque Whether or not the JComponent is opaque.
 717      */
 718     @Override
 719     public boolean isOpaque(SynthContext context) {
 720         Region region = context.getRegion();
 721         if (region == Region.COMBO_BOX ||
 722               region == Region.DESKTOP_PANE ||
 723               region == Region.DESKTOP_ICON ||
 724               region == Region.INTERNAL_FRAME ||
 725               region == Region.LIST ||
 726               region == Region.MENU_BAR ||
 727               region == Region.PANEL ||
 728               region == Region.POPUP_MENU ||
 729               region == Region.PROGRESS_BAR ||
 730               region == Region.ROOT_PANE ||
 731               region == Region.SCROLL_PANE ||
 732               region == Region.SPLIT_PANE_DIVIDER ||
 733               region == Region.TABLE ||
 734               region == Region.TEXT_AREA ||
 735               region == Region.TOOL_BAR_DRAG_WINDOW ||
 736               region == Region.TOOL_TIP ||
 737               region == Region.TREE ||
 738               region == Region.VIEWPORT) {
 739             return true;
 740         }
 741         if (!GTKLookAndFeel.is3()) {
 742             if (region == Region.EDITOR_PANE ||
 743                   region == Region.FORMATTED_TEXT_FIELD ||
 744                   region == Region.PASSWORD_FIELD ||
 745                   region == Region.SPINNER ||
 746                   region == Region.TEXT_FIELD ||
 747                   region == Region.TEXT_PANE) {
 748                 return true;
 749             }
 750         }
 751         Component c = context.getComponent();
 752         String name = c.getName();
 753         if (name == "ComboBox.renderer" || name == "ComboBox.listRenderer") {
 754             return true;
 755         }
 756         return false;
 757     }
 758 
 759     @Override
 760     public Object get(SynthContext context, Object key) {
 761         // See if this is a class specific value.
 762         String classKey = CLASS_SPECIFIC_MAP.get(key);
 763         if (classKey != null) {
 764             Object value = getClassSpecificValue(classKey);
 765             if (value != null) {
 766                 return value;
 767             }
 768         }
 769 
 770         // Is it a specific value ?
 771         if (key == "ScrollPane.viewportBorderInsets") {
 772             return getThicknessInsets(context, new Insets(0, 0, 0, 0));
 773         }
 774         else if (key == "Slider.tickColor") {
 775             return getColorForState(context, ColorType.FOREGROUND);
 776         }
 777         else if (key == "ScrollBar.minimumThumbSize") {
 778             int len =
 779                 getClassSpecificIntValue(context, "min-slider-length", 21);
 780             JScrollBar sb = (JScrollBar)context.getComponent();
 781             if (sb.getOrientation() == JScrollBar.HORIZONTAL) {
 782                 return new DimensionUIResource(len, 0);
 783             } else {
 784                 return new DimensionUIResource(0, len);
 785             }
 786         }
 787         else if (key == "Separator.thickness") {
 788             JSeparator sep = (JSeparator)context.getComponent();
 789             if (sep.getOrientation() == JSeparator.HORIZONTAL) {
 790                 return getYThickness();
 791             } else {
 792                 return getXThickness();
 793             }
 794         }
 795         else if (key == "ToolBar.separatorSize") {
 796             int size = getClassSpecificIntValue(WidgetType.TOOL_BAR,
 797                                                 "space-size", 12);
 798             return new DimensionUIResource(size, size);
 799         }
 800         else if (key == "ScrollBar.buttonSize") {
 801             JScrollBar sb = (JScrollBar)context.getComponent().getParent();
 802             boolean horiz = (sb.getOrientation() == JScrollBar.HORIZONTAL);
 803             WidgetType wt = horiz ?
 804                 WidgetType.HSCROLL_BAR : WidgetType.VSCROLL_BAR;
 805             int sliderWidth = getClassSpecificIntValue(wt, "slider-width", 14);
 806             int stepperSize = getClassSpecificIntValue(wt, "stepper-size", 14);
 807             return horiz ?
 808                 new DimensionUIResource(stepperSize, sliderWidth) :
 809                 new DimensionUIResource(sliderWidth, stepperSize);
 810         }
 811         else if (key == "ArrowButton.size") {
 812             String name = context.getComponent().getName();
 813             if (name != null && name.startsWith("Spinner")) {
 814                 // Believe it or not, the size of a spinner arrow button is
 815                 // dependent upon the size of the spinner's font.  These
 816                 // calculations come from gtkspinbutton.c (version 2.8.20),
 817                 // spin_button_get_arrow_size() method.
 818                 String pangoFontName;
 819                 synchronized (sun.awt.UNIXToolkit.GTK_LOCK) {
 820                     pangoFontName =
 821                         nativeGetPangoFontName(WidgetType.SPINNER.ordinal());
 822                 }
 823                 int arrowSize = (pangoFontName != null) ?
 824                     PangoFonts.getFontSize(pangoFontName) : 10;
 825                 return (arrowSize + (getXThickness() * 2));
 826             }
 827             // For all other kinds of arrow buttons (e.g. combobox arrow
 828             // buttons), we will simply fall back on the value of
 829             // ArrowButton.size as defined in the UIDefaults for
 830             // GTKLookAndFeel when we call UIManager.get() below...
 831         }
 832         else if ("CheckBox.iconTextGap".equals(key) ||
 833                  "RadioButton.iconTextGap".equals(key))
 834         {
 835             // The iconTextGap value needs to include "indicator-spacing"
 836             // and it also needs to leave enough space for the focus line,
 837             // which falls between the indicator icon and the text.
 838             // See getRadioInsets() and 6489585 for more details.
 839             int indicatorSpacing =
 840                 getClassSpecificIntValue(context, "indicator-spacing", 2);
 841             int focusSize =
 842                 getClassSpecificIntValue(context, "focus-line-width", 1);
 843             int focusPad =
 844                 getClassSpecificIntValue(context, "focus-padding", 1);
 845             return indicatorSpacing + focusSize + focusPad;
 846         } else if (GTKLookAndFeel.is3() && "ComboBox.forceOpaque".equals(key)) {
 847             return true;
 848         } else if ("Tree.expanderSize".equals(key)) {
 849             Object value = getClassSpecificValue("expander-size");
 850             if (value instanceof Integer) {
 851                 return (Integer)value + 4;
 852             }
 853             return null;
 854         }
 855 
 856         // Is it a stock icon ?
 857         GTKStockIcon stockIcon = null;
 858         synchronized (ICONS_MAP) {
 859             stockIcon = ICONS_MAP.get(key);
 860         }
 861 
 862         if (stockIcon != null) {
 863             return stockIcon;
 864         }
 865 
 866         // Is it another kind of value ?
 867         if (key != "engine") {
 868             // For backward compatibility we'll fallback to the UIManager.
 869             // We don't go to the UIManager for engine as the engine is GTK
 870             // specific.
 871             Object value = UIManager.get(key);
 872             if (key == "Table.rowHeight") {
 873                 int focusLineWidth = getClassSpecificIntValue(context,
 874                         "focus-line-width", 0);
 875                 if (value == null && focusLineWidth > 0) {
 876                     value = Integer.valueOf(16 + 2 * focusLineWidth);
 877                 }
 878             }
 879             return value;
 880         }
 881 
 882         // Don't call super, we don't want to pick up defaults from
 883         // SynthStyle.
 884         return null;
 885     }
 886 
 887     private Icon getStockIcon(SynthContext context, String key, int type) {
 888         TextDirection direction = TextDirection.LTR;
 889 
 890         if (context != null) {
 891             ComponentOrientation co = context.getComponent().
 892                                               getComponentOrientation();
 893 
 894             if (co != null && !co.isLeftToRight()) {
 895                 direction = TextDirection.RTL;
 896             }
 897         }
 898 
 899         // First try loading a theme-specific icon using the native
 900         // GTK libraries (native GTK handles the resizing for us).
 901         Icon icon = getStyleSpecificIcon(key, direction, type);
 902         if (icon != null) {
 903             return icon;
 904         }
 905 
 906         // In a failure case where native GTK (unexpectedly) returns a
 907         // null icon, we can try loading a default icon as a fallback.
 908         String propName = ICON_PROPERTY_PREFIX + key + '.' + type + '.' +
 909                           (direction == TextDirection.RTL ? "rtl" : "ltr");
 910         Image img = (Image)
 911             Toolkit.getDefaultToolkit().getDesktopProperty(propName);
 912         if (img != null) {
 913             return new ImageIcon(img);
 914         }
 915 
 916         // In an extreme failure situation, just return null (callers are
 917         // already prepared to handle a null icon, so the worst that can
 918         // happen is that an icon won't be included in the button/dialog).
 919         return null;
 920     }
 921 
 922     private Icon getStyleSpecificIcon(String key,
 923                                       TextDirection direction, int type)
 924     {
 925         UNIXToolkit tk = (UNIXToolkit)Toolkit.getDefaultToolkit();
 926         Image img =
 927             tk.getStockIcon(widgetType, key, type, direction.ordinal(), null);
 928         return (img != null) ? new ImageIcon(img) : null;
 929     }
 930 
 931     static class GTKStockIconInfo {
 932         private static Map<String,Integer> ICON_TYPE_MAP;
 933         private static final Object ICON_SIZE_KEY = new StringBuffer("IconSize");
 934 
 935         private static Dimension[] getIconSizesMap() {
 936             AppContext appContext = AppContext.getAppContext();
 937             Dimension[] iconSizes = (Dimension[])appContext.get(ICON_SIZE_KEY);
 938 
 939             if (iconSizes == null) {
 940                 iconSizes = new Dimension[7];
 941                 iconSizes[0] = null;                  // GTK_ICON_SIZE_INVALID
 942                 iconSizes[1] = new Dimension(16, 16); // GTK_ICON_SIZE_MENU
 943                 iconSizes[2] = new Dimension(18, 18); // GTK_ICON_SIZE_SMALL_TOOLBAR
 944                 iconSizes[3] = new Dimension(24, 24); // GTK_ICON_SIZE_LARGE_TOOLBAR
 945                 iconSizes[4] = new Dimension(20, 20); // GTK_ICON_SIZE_BUTTON
 946                 iconSizes[5] = new Dimension(32, 32); // GTK_ICON_SIZE_DND
 947                 iconSizes[6] = new Dimension(48, 48); // GTK_ICON_SIZE_DIALOG
 948                 appContext.put(ICON_SIZE_KEY, iconSizes);
 949             }
 950             return iconSizes;
 951         }
 952 
 953         /**
 954          * Return the size of a particular icon type (logical size)
 955          *
 956          * @param type icon type (GtkIconSize value)
 957          * @return a Dimension object, or null if lsize is invalid
 958          */
 959         public static Dimension getIconSize(int type) {
 960             Dimension[] iconSizes = getIconSizesMap();
 961             return type >= 0 && type < iconSizes.length ?
 962                 iconSizes[type] : null;
 963         }
 964 
 965         /**
 966          * Change icon size in a type to size mapping. This is called by code
 967          * that parses the gtk-icon-sizes setting
 968          *
 969          * @param type icon type (GtkIconSize value)
 970          * @param w the new icon width
 971          * @param h the new icon height
 972          */
 973         public static void setIconSize(int type, int w, int h) {
 974             Dimension[] iconSizes = getIconSizesMap();
 975             if (type >= 0 && type < iconSizes.length) {
 976                 iconSizes[type] = new Dimension(w, h);
 977             }
 978         }
 979 
 980         /**
 981          * Return icon type (GtkIconSize value) given a symbolic name which can
 982          * occur in a theme file.
 983          *
 984          * @param size symbolic name, e.g. gtk-button
 985          * @return icon type. Valid types are 1 to 6
 986          */
 987         public static int getIconType(String size) {
 988             if (size == null) {
 989                 return UNDEFINED;
 990             }
 991             if (ICON_TYPE_MAP == null) {
 992                 initIconTypeMap();
 993             }
 994             Integer n = ICON_TYPE_MAP.get(size);
 995             return n != null ? n.intValue() : UNDEFINED;
 996         }
 997 
 998         private static void initIconTypeMap() {
 999             ICON_TYPE_MAP = new HashMap<String,Integer>();
1000             ICON_TYPE_MAP.put("gtk-menu", Integer.valueOf(1));
1001             ICON_TYPE_MAP.put("gtk-small-toolbar", Integer.valueOf(2));
1002             ICON_TYPE_MAP.put("gtk-large-toolbar", Integer.valueOf(3));
1003             ICON_TYPE_MAP.put("gtk-button", Integer.valueOf(4));
1004             ICON_TYPE_MAP.put("gtk-dnd", Integer.valueOf(5));
1005             ICON_TYPE_MAP.put("gtk-dialog", Integer.valueOf(6));
1006         }
1007 
1008     }
1009 
1010     /**
1011      * An Icon that is fetched using getStockIcon.
1012      */
1013     private static class GTKStockIcon extends SynthIcon {
1014         private String key;
1015         private int size;
1016         private boolean loadedLTR;
1017         private boolean loadedRTL;
1018         private Icon ltrIcon;
1019         private Icon rtlIcon;
1020         private SynthStyle style;
1021 
1022         GTKStockIcon(String key, int size) {
1023             this.key = key;
1024             this.size = size;
1025         }
1026 
1027         public void paintIcon(SynthContext context, Graphics g, int x,
1028                               int y, int w, int h) {
1029             Icon icon = getIcon(context);
1030 
1031             if (icon != null) {
1032                 if (context == null) {
1033                     icon.paintIcon(null, g, x, y);
1034                 }
1035                 else {
1036                     icon.paintIcon(context.getComponent(), g, x, y);
1037                 }
1038             }
1039         }
1040 
1041         public int getIconWidth(SynthContext context) {
1042             Icon icon = getIcon(context);
1043 
1044             if (icon != null) {
1045                 return icon.getIconWidth();
1046             }
1047             return 0;
1048         }
1049 
1050         public int getIconHeight(SynthContext context) {
1051             Icon icon = getIcon(context);
1052 
1053             if (icon != null) {
1054                 return icon.getIconHeight();
1055             }
1056             return 0;
1057         }
1058 
1059         private Icon getIcon(SynthContext context) {
1060             if (context != null) {
1061                 ComponentOrientation co = context.getComponent().
1062                                                   getComponentOrientation();
1063                 SynthStyle style = context.getStyle();
1064 
1065                 if (style != this.style) {
1066                     this.style = style;
1067                     loadedLTR = loadedRTL = false;
1068                 }
1069                 if (co == null || co.isLeftToRight()) {
1070                     if (!loadedLTR) {
1071                         loadedLTR = true;
1072                         ltrIcon = ((GTKStyle)context.getStyle()).
1073                                   getStockIcon(context, key, size);
1074                     }
1075                     return ltrIcon;
1076                 }
1077                 else if (!loadedRTL) {
1078                     loadedRTL = true;
1079                     rtlIcon = ((GTKStyle)context.getStyle()).
1080                               getStockIcon(context, key,size);
1081                 }
1082                 return rtlIcon;
1083             }
1084             return ltrIcon;
1085         }
1086     }
1087 
1088     /**
1089      * GTKLazyValue is a slimmed down version of <code>ProxyLaxyValue</code>.
1090      * The code is duplicate so that it can get at the package private
1091      * classes in gtk.
1092      */
1093     static class GTKLazyValue implements UIDefaults.LazyValue {
1094         /**
1095          * Name of the class to create.
1096          */
1097         private String className;
1098         private String methodName;
1099 
1100         GTKLazyValue(String name) {
1101             this(name, null);
1102         }
1103 
1104         GTKLazyValue(String name, String methodName) {
1105             this.className = name;
1106             this.methodName = methodName;
1107         }
1108 
1109         public Object createValue(UIDefaults table) {
1110             try {
1111                 Class c = Class.forName(className, true,Thread.currentThread().
1112                                         getContextClassLoader());
1113 
1114                 if (methodName == null) {
1115                     return c.newInstance();
1116                 }
1117                 Method m = c.getMethod(methodName, (Class[])null);
1118 
1119                 return m.invoke(c, (Object[])null);
1120             } catch (ClassNotFoundException cnfe) {
1121             } catch (IllegalAccessException iae) {
1122             } catch (InvocationTargetException ite) {
1123             } catch (NoSuchMethodException nsme) {
1124             } catch (InstantiationException ie) {
1125             }
1126             return null;
1127         }
1128     }
1129 
1130     static {
1131         CLASS_SPECIFIC_MAP = new HashMap<String,String>();
1132         CLASS_SPECIFIC_MAP.put("Slider.thumbHeight", "slider-width");
1133         CLASS_SPECIFIC_MAP.put("Slider.thumbWidth", "slider-length");
1134         CLASS_SPECIFIC_MAP.put("Slider.trackBorder", "trough-border");
1135         CLASS_SPECIFIC_MAP.put("SplitPane.size", "handle-size");
1136         CLASS_SPECIFIC_MAP.put("ScrollBar.thumbHeight", "slider-width");
1137         CLASS_SPECIFIC_MAP.put("ScrollBar.width", "slider-width");
1138         CLASS_SPECIFIC_MAP.put("TextArea.caretForeground", "cursor-color");
1139         CLASS_SPECIFIC_MAP.put("TextArea.caretAspectRatio", "cursor-aspect-ratio");
1140         CLASS_SPECIFIC_MAP.put("TextField.caretForeground", "cursor-color");
1141         CLASS_SPECIFIC_MAP.put("TextField.caretAspectRatio", "cursor-aspect-ratio");
1142         CLASS_SPECIFIC_MAP.put("PasswordField.caretForeground", "cursor-color");
1143         CLASS_SPECIFIC_MAP.put("PasswordField.caretAspectRatio", "cursor-aspect-ratio");
1144         CLASS_SPECIFIC_MAP.put("FormattedTextField.caretForeground", "cursor-color");
1145         CLASS_SPECIFIC_MAP.put("FormattedTextField.caretAspectRatio", "cursor-aspect-");
1146         CLASS_SPECIFIC_MAP.put("TextPane.caretForeground", "cursor-color");
1147         CLASS_SPECIFIC_MAP.put("TextPane.caretAspectRatio", "cursor-aspect-ratio");
1148         CLASS_SPECIFIC_MAP.put("EditorPane.caretForeground", "cursor-color");
1149         CLASS_SPECIFIC_MAP.put("EditorPane.caretAspectRatio", "cursor-aspect-ratio");
1150 
1151         ICONS_MAP = new HashMap<String, GTKStockIcon>();
1152         ICONS_MAP.put("FileChooser.cancelIcon", new GTKStockIcon("gtk-cancel", 4));
1153         ICONS_MAP.put("FileChooser.okIcon",     new GTKStockIcon("gtk-ok",     4));
1154         ICONS_MAP.put("OptionPane.errorIcon", new GTKStockIcon("gtk-dialog-error", 6));
1155         ICONS_MAP.put("OptionPane.informationIcon", new GTKStockIcon("gtk-dialog-info", 6));
1156         ICONS_MAP.put("OptionPane.warningIcon", new GTKStockIcon("gtk-dialog-warning", 6));
1157         ICONS_MAP.put("OptionPane.questionIcon", new GTKStockIcon("gtk-dialog-question", 6));
1158         ICONS_MAP.put("OptionPane.yesIcon", new GTKStockIcon("gtk-yes", 4));
1159         ICONS_MAP.put("OptionPane.noIcon", new GTKStockIcon("gtk-no", 4));
1160         ICONS_MAP.put("OptionPane.cancelIcon", new GTKStockIcon("gtk-cancel", 4));
1161         ICONS_MAP.put("OptionPane.okIcon", new GTKStockIcon("gtk-ok", 4));
1162     }
1163 }