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