1 /*
   2  * Copyright (c) 2002, 2015, 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     protected Font getFontForState(SynthContext context) {
 286         return font;
 287     }
 288 
 289     /**
 290      * Returns the X thickness to use for this GTKStyle.
 291      *
 292      * @return x thickness.
 293      */
 294     int getXThickness() {
 295         return xThickness;
 296     }
 297 
 298     /**
 299      * Returns the Y thickness to use for this GTKStyle.
 300      *
 301      * @return y thickness.
 302      */
 303     int getYThickness() {
 304         return yThickness;
 305     }
 306 
 307     /**
 308      * Returns the Insets. If <code>insets</code> is non-null the resulting
 309      * insets will be placed in it, otherwise a new Insets object will be
 310      * created and returned.
 311      *
 312      * @param context SynthContext identifying requestor
 313      * @param insets Where to place Insets
 314      * @return Insets.
 315      */
 316     @Override
 317     public Insets getInsets(SynthContext state, Insets insets) {
 318         Region id = state.getRegion();
 319         JComponent component = state.getComponent();
 320         String name = (id.isSubregion()) ? null : component.getName();
 321 
 322         if (insets == null) {
 323             insets = new Insets(0, 0, 0, 0);
 324         } else {
 325             insets.top = insets.bottom = insets.left = insets.right = 0;
 326         }
 327 
 328         if (id == Region.ARROW_BUTTON || id == Region.BUTTON ||
 329                 id == Region.TOGGLE_BUTTON) {
 330             if ("Spinner.previousButton" == name ||
 331                     "Spinner.nextButton" == name) {
 332                 return getSimpleInsets(state, insets, 1);
 333             } else {
 334                 return getButtonInsets(state, insets);
 335             }
 336         }
 337         else if (id == Region.CHECK_BOX || id == Region.RADIO_BUTTON) {
 338             return getRadioInsets(state, insets);
 339         }
 340         else if (id == Region.MENU_BAR) {
 341             return getMenuBarInsets(state, insets);
 342         }
 343         else if (id == Region.MENU ||
 344                  id == Region.MENU_ITEM ||
 345                  id == Region.CHECK_BOX_MENU_ITEM ||
 346                  id == Region.RADIO_BUTTON_MENU_ITEM) {
 347             return getMenuItemInsets(state, insets);
 348         }
 349         else if (id == Region.FORMATTED_TEXT_FIELD) {
 350             return getTextFieldInsets(state, insets);
 351         }
 352         else if (id == Region.INTERNAL_FRAME) {
 353             insets = Metacity.INSTANCE.getBorderInsets(state, insets);
 354         }
 355         else if (id == Region.LABEL) {
 356             if ("TableHeader.renderer" == name) {
 357                 return getButtonInsets(state, insets);
 358             }
 359             else if (component instanceof ListCellRenderer) {
 360                 return getTextFieldInsets(state, insets);
 361             }
 362             else if ("Tree.cellRenderer" == name) {
 363                 return getSimpleInsets(state, insets, 1);
 364             }
 365         }
 366         else if (id == Region.OPTION_PANE) {
 367             return getSimpleInsets(state, insets, 6);
 368         }
 369         else if (id == Region.POPUP_MENU) {
 370             return getSimpleInsets(state, insets, 2);
 371         }
 372         else if (id == Region.PROGRESS_BAR || id == Region.SLIDER ||
 373                  id == Region.TABBED_PANE  || id == Region.TABBED_PANE_CONTENT ||
 374                  id == Region.TOOL_BAR     ||
 375                  id == Region.TOOL_BAR_DRAG_WINDOW ||
 376                  id == Region.TOOL_TIP) {
 377             return getThicknessInsets(state, insets);
 378         }
 379         else if (id == Region.SCROLL_BAR) {
 380             return getScrollBarInsets(state, insets);
 381         }
 382         else if (id == Region.SLIDER_TRACK) {
 383             return getSliderTrackInsets(state, insets);
 384         }
 385         else if (id == Region.TABBED_PANE_TAB) {
 386             return getTabbedPaneTabInsets(state, insets);
 387         }
 388         else if (id == Region.TEXT_FIELD || id == Region.PASSWORD_FIELD) {
 389             if (name == "Tree.cellEditor") {
 390                 return getSimpleInsets(state, insets, 1);
 391             }
 392             return getTextFieldInsets(state, insets);
 393         } else if (id == Region.SEPARATOR ||
 394                    id == Region.POPUP_MENU_SEPARATOR ||
 395                    id == Region.TOOL_BAR_SEPARATOR) {
 396             return getSeparatorInsets(state, insets);
 397         } else if (id == GTKEngine.CustomRegion.TITLED_BORDER) {
 398             return getThicknessInsets(state, insets);
 399         }
 400         return insets;
 401     }
 402 
 403     private Insets getButtonInsets(SynthContext context, Insets insets) {
 404         // The following calculations are derived from gtkbutton.c
 405         // (GTK+ version 2.8.20), gtk_button_size_allocate() method.
 406         int CHILD_SPACING = 1;
 407         int focusSize = getClassSpecificIntValue(context, "focus-line-width",1);
 408         int focusPad = getClassSpecificIntValue(context, "focus-padding", 1);
 409         int xThickness = getXThickness();
 410         int yThickness = getYThickness();
 411         int w = focusSize + focusPad + xThickness + CHILD_SPACING;
 412         int h = focusSize + focusPad + yThickness + CHILD_SPACING;
 413         insets.left = insets.right = w;
 414         insets.top = insets.bottom = h;
 415 
 416         Component component = context.getComponent();
 417         if ((component instanceof JButton) &&
 418             !(component.getParent() instanceof JToolBar) &&
 419             ((JButton)component).isDefaultCapable())
 420         {
 421             // Include the default border insets, but only for JButtons
 422             // that are default capable.  Note that
 423             // JButton.getDefaultCapable() returns true by default, but
 424             // GtkToolButtons are never default capable, so we skip this
 425             // step if the button is contained in a toolbar.
 426             Insets defaultInsets = getClassSpecificInsetsValue(context,
 427                           "default-border", BUTTON_DEFAULT_BORDER_INSETS);
 428             insets.left += defaultInsets.left;
 429             insets.right += defaultInsets.right;
 430             insets.top += defaultInsets.top;
 431             insets.bottom += defaultInsets.bottom;
 432         }
 433 
 434         return insets;
 435     }
 436 
 437     /*
 438      * This is used for both RADIO_BUTTON and CHECK_BOX.
 439      */
 440     private Insets getRadioInsets(SynthContext context, Insets insets) {
 441         // The following calculations are derived from gtkcheckbutton.c
 442         // (GTK+ version 2.8.20), gtk_check_button_size_allocate() method.
 443         int focusSize =
 444             getClassSpecificIntValue(context, "focus-line-width", 1);
 445         int focusPad =
 446             getClassSpecificIntValue(context, "focus-padding", 1);
 447         int totalFocus = focusSize + focusPad;
 448 
 449         // Note: GTKIconFactory.DelegateIcon will have already included the
 450         // "indicator-spacing" value in the size of the indicator icon,
 451         // which explains why we use zero as the left inset (or right inset
 452         // in the RTL case); see 6489585 for more details.
 453         insets.top    = totalFocus;
 454         insets.bottom = totalFocus;
 455         if (context.getComponent().getComponentOrientation().isLeftToRight()) {
 456             insets.left  = 0;
 457             insets.right = totalFocus;
 458         } else {
 459             insets.left  = totalFocus;
 460             insets.right = 0;
 461         }
 462 
 463         return insets;
 464     }
 465 
 466     private Insets getMenuBarInsets(SynthContext context, Insets insets) {
 467         // The following calculations are derived from gtkmenubar.c
 468         // (GTK+ version 2.8.20), gtk_menu_bar_size_allocate() method.
 469         int internalPadding = getClassSpecificIntValue(context,
 470                                                        "internal-padding", 1);
 471         int xThickness = getXThickness();
 472         int yThickness = getYThickness();
 473         insets.left = insets.right = xThickness + internalPadding;
 474         insets.top = insets.bottom = yThickness + internalPadding;
 475         return insets;
 476     }
 477 
 478     private Insets getMenuItemInsets(SynthContext context, Insets insets) {
 479         // The following calculations are derived from gtkmenuitem.c
 480         // (GTK+ version 2.8.20), gtk_menu_item_size_allocate() method.
 481         int horizPadding = getClassSpecificIntValue(context,
 482                                                     "horizontal-padding", 3);
 483         int xThickness = getXThickness();
 484         int yThickness = getYThickness();
 485         insets.left = insets.right = xThickness + horizPadding;
 486         insets.top = insets.bottom = yThickness;
 487         return insets;
 488     }
 489 
 490     private Insets getThicknessInsets(SynthContext context, Insets insets) {
 491         insets.left = insets.right = getXThickness();
 492         insets.top = insets.bottom = getYThickness();
 493         return insets;
 494     }
 495 
 496     private Insets getSeparatorInsets(SynthContext context, Insets insets) {
 497         int horizPadding = 0;
 498         if (context.getRegion() == Region.POPUP_MENU_SEPARATOR) {
 499             horizPadding =
 500                 getClassSpecificIntValue(context, "horizontal-padding", 3);
 501         }
 502         insets.right = insets.left = getXThickness() + horizPadding;
 503         insets.top = insets.bottom = getYThickness();
 504         return insets;
 505     }
 506 
 507     private Insets getSliderTrackInsets(SynthContext context, Insets insets) {
 508         int focusSize = getClassSpecificIntValue(context, "focus-line-width", 1);
 509         int focusPad = getClassSpecificIntValue(context, "focus-padding", 1);
 510         insets.top = insets.bottom =
 511                 insets.left = insets.right = focusSize + focusPad;
 512         return insets;
 513     }
 514 
 515     private Insets getSimpleInsets(SynthContext context, Insets insets, int n) {
 516         insets.top = insets.bottom = insets.right = insets.left = n;
 517         return insets;
 518     }
 519 
 520     private Insets getTabbedPaneTabInsets(SynthContext context, Insets insets) {
 521         int xThickness = getXThickness();
 522         int yThickness = getYThickness();
 523         int focusSize = getClassSpecificIntValue(context, "focus-line-width",1);
 524         int pad = 2;
 525 
 526         insets.left = insets.right = focusSize + pad + xThickness;
 527         insets.top = insets.bottom = focusSize + pad + yThickness;
 528         return insets;
 529     }
 530 
 531     // NOTE: this is called for ComboBox, and FormattedTextField also
 532     private Insets getTextFieldInsets(SynthContext context, Insets insets) {
 533         insets = getClassSpecificInsetsValue(context, "inner-border",
 534                                     getSimpleInsets(context, insets, 2));
 535 
 536         int xThickness = getXThickness();
 537         int yThickness = getYThickness();
 538         boolean interiorFocus =
 539                 getClassSpecificBoolValue(context, "interior-focus", true);
 540         int focusSize = 0;
 541 
 542         if (!interiorFocus) {
 543             focusSize = getClassSpecificIntValue(context, "focus-line-width",1);
 544         }
 545 
 546         insets.left   += focusSize + xThickness;
 547         insets.right  += focusSize + xThickness;
 548         insets.top    += focusSize + yThickness;
 549         insets.bottom += focusSize + yThickness;
 550         return insets;
 551     }
 552 
 553     private Insets getScrollBarInsets(SynthContext context, Insets insets) {
 554         int troughBorder =
 555             getClassSpecificIntValue(context, "trough-border", 1);
 556         insets.left = insets.right = insets.top = insets.bottom = troughBorder;
 557 
 558         JComponent c = context.getComponent();
 559         if (c.getParent() instanceof JScrollPane) {
 560             // This scrollbar is part of a scrollpane; use only the
 561             // "scrollbar-spacing" style property to determine the padding
 562             // between the scrollbar and its parent scrollpane.
 563             int spacing =
 564                 getClassSpecificIntValue(WidgetType.SCROLL_PANE,
 565                                          "scrollbar-spacing", 3);
 566             if (((JScrollBar)c).getOrientation() == JScrollBar.HORIZONTAL) {
 567                 insets.top += spacing;
 568             } else {
 569                 if (c.getComponentOrientation().isLeftToRight()) {
 570                     insets.left += spacing;
 571                 } else {
 572                     insets.right += spacing;
 573                 }
 574             }
 575         } else {
 576             // This is a standalone scrollbar; leave enough room for the
 577             // focus line in addition to the trough border.
 578             if (c.isFocusable()) {
 579                 int focusSize =
 580                     getClassSpecificIntValue(context, "focus-line-width", 1);
 581                 int focusPad =
 582                     getClassSpecificIntValue(context, "focus-padding", 1);
 583                 int totalFocus = focusSize + focusPad;
 584                 insets.left   += totalFocus;
 585                 insets.right  += totalFocus;
 586                 insets.top    += totalFocus;
 587                 insets.bottom += totalFocus;
 588             }
 589         }
 590         return insets;
 591     }
 592 
 593     /**
 594      * Returns the value for a class specific property for a particular
 595      * WidgetType.  This method is useful in those cases where we need to
 596      * fetch a value for a Region that is not associated with the component
 597      * currently in use (e.g. we need to figure out the insets for a
 598      * SCROLL_BAR, but certain values can only be extracted from a
 599      * SCROLL_PANE region).
 600      *
 601      * @param wt WidgetType for which to fetch the value
 602      * @param key Key identifying class specific value
 603      * @return Value, or null if one has not been defined
 604      */
 605     private static Object getClassSpecificValue(WidgetType wt, String key) {
 606         synchronized (UNIXToolkit.GTK_LOCK) {
 607             return nativeGetClassValue(wt.ordinal(), key);
 608         }
 609     }
 610 
 611     /**
 612      * Convenience method to get a class specific integer value for
 613      * a particular WidgetType.
 614      *
 615      * @param wt WidgetType for which to fetch the value
 616      * @param key Key identifying class specific value
 617      * @param defaultValue Returned if there is no value for the specified
 618      *        type
 619      * @return Value, or defaultValue if <code>key</code> is not defined
 620      */
 621     private static int getClassSpecificIntValue(WidgetType wt, String key,
 622                                                 int defaultValue)
 623     {
 624         Object value = getClassSpecificValue(wt, key);
 625         if (value instanceof Number) {
 626             return ((Number)value).intValue();
 627         }
 628         return defaultValue;
 629     }
 630 
 631     /**
 632      * Returns the value for a class specific property. A class specific value
 633      * is a value that will be picked up based on class hierarchy.
 634      *
 635      * @param key Key identifying class specific value
 636      * @return Value, or null if one has not been defined.
 637      */
 638     Object getClassSpecificValue(String key) {
 639         synchronized (sun.awt.UNIXToolkit.GTK_LOCK) {
 640             return nativeGetClassValue(widgetType, key);
 641         }
 642     }
 643 
 644     /**
 645      * Convenience method to get a class specific integer value.
 646      *
 647      * @param context SynthContext identifying requestor
 648      * @param key Key identifying class specific value
 649      * @param defaultValue Returned if there is no value for the specified
 650      *        type
 651      * @return Value, or defaultValue if <code>key</code> is not defined
 652      */
 653     int getClassSpecificIntValue(SynthContext context, String key,
 654                                  int defaultValue)
 655     {
 656         Object value = getClassSpecificValue(key);
 657 
 658         if (value instanceof Number) {
 659             return ((Number)value).intValue();
 660         }
 661         return defaultValue;
 662     }
 663 
 664     /**
 665      * Convenience method to get a class specific Insets value.
 666      *
 667      * @param context SynthContext identifying requestor
 668      * @param key Key identifying class specific value
 669      * @param defaultValue Returned if there is no value for the specified
 670      *        type
 671      * @return Value, or defaultValue if <code>key</code> is not defined
 672      */
 673     Insets getClassSpecificInsetsValue(SynthContext context, String key,
 674                                        Insets defaultValue)
 675     {
 676         Object value = getClassSpecificValue(key);
 677 
 678         if (value instanceof Insets) {
 679             return (Insets)value;
 680         }
 681         return defaultValue;
 682     }
 683 
 684     /**
 685      * Convenience method to get a class specific Boolean value.
 686      *
 687      * @param context SynthContext identifying requestor
 688      * @param key Key identifying class specific value
 689      * @param defaultValue Returned if there is no value for the specified
 690      *        type
 691      * @return Value, or defaultValue if <code>key</code> is not defined
 692      */
 693     boolean getClassSpecificBoolValue(SynthContext context, String key,
 694                                       boolean defaultValue)
 695     {
 696         Object value = getClassSpecificValue(key);
 697 
 698         if (value instanceof Boolean) {
 699             return ((Boolean)value).booleanValue();
 700         }
 701         return defaultValue;
 702     }
 703 
 704     /**
 705      * Returns the value to initialize the opacity property of the Component
 706      * to. A Style should NOT assume the opacity will remain this value, the
 707      * developer may reset it or override it.
 708      *
 709      * @param context SynthContext identifying requestor
 710      * @return opaque Whether or not the JComponent is opaque.
 711      */
 712     @Override
 713     public boolean isOpaque(SynthContext context) {
 714         Region region = context.getRegion();
 715         if (region == Region.COMBO_BOX ||
 716               region == Region.DESKTOP_PANE ||
 717               region == Region.DESKTOP_ICON ||
 718               region == Region.EDITOR_PANE ||
 719               region == Region.FORMATTED_TEXT_FIELD ||
 720               region == Region.INTERNAL_FRAME ||
 721               region == Region.LIST ||
 722               region == Region.MENU_BAR ||
 723               region == Region.PANEL ||
 724               region == Region.PASSWORD_FIELD ||
 725               region == Region.POPUP_MENU ||
 726               region == Region.PROGRESS_BAR ||
 727               region == Region.ROOT_PANE ||
 728               region == Region.SCROLL_PANE ||
 729               region == Region.SPINNER ||
 730               region == Region.SPLIT_PANE_DIVIDER ||
 731               region == Region.TABLE ||
 732               region == Region.TEXT_AREA ||
 733               region == Region.TEXT_FIELD ||
 734               region == Region.TEXT_PANE ||
 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         Component c = context.getComponent();
 742         String name = c.getName();
 743         if (name == "ComboBox.renderer" || name == "ComboBox.listRenderer") {
 744             return true;
 745         }
 746         return false;
 747     }
 748 
 749     @Override
 750     public Object get(SynthContext context, Object key) {
 751         // See if this is a class specific value.
 752         String classKey = CLASS_SPECIFIC_MAP.get(key);
 753         if (classKey != null) {
 754             Object value = getClassSpecificValue(classKey);
 755             if (value != null) {
 756                 return value;
 757             }
 758         }
 759 
 760         // Is it a specific value ?
 761         if (key == "ScrollPane.viewportBorderInsets") {
 762             return getThicknessInsets(context, new Insets(0, 0, 0, 0));
 763         }
 764         else if (key == "Slider.tickColor") {
 765             return getColorForState(context, ColorType.FOREGROUND);
 766         }
 767         else if (key == "ScrollBar.minimumThumbSize") {
 768             int len =
 769                 getClassSpecificIntValue(context, "min-slider-length", 21);
 770             JScrollBar sb = (JScrollBar)context.getComponent();
 771             if (sb.getOrientation() == JScrollBar.HORIZONTAL) {
 772                 return new DimensionUIResource(len, 0);
 773             } else {
 774                 return new DimensionUIResource(0, len);
 775             }
 776         }
 777         else if (key == "Separator.thickness") {
 778             JSeparator sep = (JSeparator)context.getComponent();
 779             if (sep.getOrientation() == JSeparator.HORIZONTAL) {
 780                 return getYThickness();
 781             } else {
 782                 return getXThickness();
 783             }
 784         }
 785         else if (key == "ToolBar.separatorSize") {
 786             int size = getClassSpecificIntValue(WidgetType.TOOL_BAR,
 787                                                 "space-size", 12);
 788             return new DimensionUIResource(size, size);
 789         }
 790         else if (key == "ScrollBar.buttonSize") {
 791             JScrollBar sb = (JScrollBar)context.getComponent().getParent();
 792             boolean horiz = (sb.getOrientation() == JScrollBar.HORIZONTAL);
 793             WidgetType wt = horiz ?
 794                 WidgetType.HSCROLL_BAR : WidgetType.VSCROLL_BAR;
 795             int sliderWidth = getClassSpecificIntValue(wt, "slider-width", 14);
 796             int stepperSize = getClassSpecificIntValue(wt, "stepper-size", 14);
 797             return horiz ?
 798                 new DimensionUIResource(stepperSize, sliderWidth) :
 799                 new DimensionUIResource(sliderWidth, stepperSize);
 800         }
 801         else if (key == "ArrowButton.size") {
 802             String name = context.getComponent().getName();
 803             if (name != null && name.startsWith("Spinner")) {
 804                 // Believe it or not, the size of a spinner arrow button is
 805                 // dependent upon the size of the spinner's font.  These
 806                 // calculations come from gtkspinbutton.c (version 2.8.20),
 807                 // spin_button_get_arrow_size() method.
 808                 String pangoFontName;
 809                 synchronized (sun.awt.UNIXToolkit.GTK_LOCK) {
 810                     pangoFontName =
 811                         nativeGetPangoFontName(WidgetType.SPINNER.ordinal());
 812                 }
 813                 int arrowSize = (pangoFontName != null) ?
 814                     PangoFonts.getFontSize(pangoFontName) : 10;
 815                 return (arrowSize + (getXThickness() * 2));
 816             }
 817             // For all other kinds of arrow buttons (e.g. combobox arrow
 818             // buttons), we will simply fall back on the value of
 819             // ArrowButton.size as defined in the UIDefaults for
 820             // GTKLookAndFeel when we call UIManager.get() below...
 821         }
 822         else if ("CheckBox.iconTextGap".equals(key) ||
 823                  "RadioButton.iconTextGap".equals(key))
 824         {
 825             // The iconTextGap value needs to include "indicator-spacing"
 826             // and it also needs to leave enough space for the focus line,
 827             // which falls between the indicator icon and the text.
 828             // See getRadioInsets() and 6489585 for more details.
 829             int indicatorSpacing =
 830                 getClassSpecificIntValue(context, "indicator-spacing", 2);
 831             int focusSize =
 832                 getClassSpecificIntValue(context, "focus-line-width", 1);
 833             int focusPad =
 834                 getClassSpecificIntValue(context, "focus-padding", 1);
 835             return indicatorSpacing + focusSize + focusPad;
 836         }
 837 
 838         // Is it a stock icon ?
 839         GTKStockIcon stockIcon = null;
 840         synchronized (ICONS_MAP) {
 841             stockIcon = ICONS_MAP.get(key);
 842         }
 843 
 844         if (stockIcon != null) {
 845             return stockIcon;
 846         }
 847 
 848         // Is it another kind of value ?
 849         if (key != "engine") {
 850             // For backward compatibility we'll fallback to the UIManager.
 851             // We don't go to the UIManager for engine as the engine is GTK
 852             // specific.
 853             Object value = UIManager.get(key);
 854             if (key == "Table.rowHeight") {
 855                 int focusLineWidth = getClassSpecificIntValue(context,
 856                         "focus-line-width", 0);
 857                 if (value == null && focusLineWidth > 0) {
 858                     value = Integer.valueOf(16 + 2 * focusLineWidth);
 859                 }
 860             }
 861             return value;
 862         }
 863 
 864         // Don't call super, we don't want to pick up defaults from
 865         // SynthStyle.
 866         return null;
 867     }
 868 
 869     private Icon getStockIcon(SynthContext context, String key, int type) {
 870         TextDirection direction = TextDirection.LTR;
 871 
 872         if (context != null) {
 873             ComponentOrientation co = context.getComponent().
 874                                               getComponentOrientation();
 875 
 876             if (co != null && !co.isLeftToRight()) {
 877                 direction = TextDirection.RTL;
 878             }
 879         }
 880 
 881         // First try loading a theme-specific icon using the native
 882         // GTK libraries (native GTK handles the resizing for us).
 883         Icon icon = getStyleSpecificIcon(key, direction, type);
 884         if (icon != null) {
 885             return icon;
 886         }
 887 
 888         // In a failure case where native GTK (unexpectedly) returns a
 889         // null icon, we can try loading a default icon as a fallback.
 890         String propName = ICON_PROPERTY_PREFIX + key + '.' + type + '.' +
 891                           (direction == TextDirection.RTL ? "rtl" : "ltr");
 892         Image img = (Image)
 893             Toolkit.getDefaultToolkit().getDesktopProperty(propName);
 894         if (img != null) {
 895             return new ImageIcon(img);
 896         }
 897 
 898         // In an extreme failure situation, just return null (callers are
 899         // already prepared to handle a null icon, so the worst that can
 900         // happen is that an icon won't be included in the button/dialog).
 901         return null;
 902     }
 903 
 904     private Icon getStyleSpecificIcon(String key,
 905                                       TextDirection direction, int type)
 906     {
 907         UNIXToolkit tk = (UNIXToolkit)Toolkit.getDefaultToolkit();
 908         Image img =
 909             tk.getStockIcon(widgetType, key, type, direction.ordinal(), null);
 910         return (img != null) ? new ImageIcon(img) : null;
 911     }
 912 
 913     static class GTKStockIconInfo {
 914         private static Map<String,Integer> ICON_TYPE_MAP;
 915         private static final Object ICON_SIZE_KEY = new StringBuffer("IconSize");
 916 
 917         private static Dimension[] getIconSizesMap() {
 918             AppContext appContext = AppContext.getAppContext();
 919             Dimension[] iconSizes = (Dimension[])appContext.get(ICON_SIZE_KEY);
 920 
 921             if (iconSizes == null) {
 922                 iconSizes = new Dimension[7];
 923                 iconSizes[0] = null;                  // GTK_ICON_SIZE_INVALID
 924                 iconSizes[1] = new Dimension(16, 16); // GTK_ICON_SIZE_MENU
 925                 iconSizes[2] = new Dimension(18, 18); // GTK_ICON_SIZE_SMALL_TOOLBAR
 926                 iconSizes[3] = new Dimension(24, 24); // GTK_ICON_SIZE_LARGE_TOOLBAR
 927                 iconSizes[4] = new Dimension(20, 20); // GTK_ICON_SIZE_BUTTON
 928                 iconSizes[5] = new Dimension(32, 32); // GTK_ICON_SIZE_DND
 929                 iconSizes[6] = new Dimension(48, 48); // GTK_ICON_SIZE_DIALOG
 930                 appContext.put(ICON_SIZE_KEY, iconSizes);
 931             }
 932             return iconSizes;
 933         }
 934 
 935         /**
 936          * Return the size of a particular icon type (logical size)
 937          *
 938          * @param type icon type (GtkIconSize value)
 939          * @return a Dimension object, or null if lsize is invalid
 940          */
 941         public static Dimension getIconSize(int type) {
 942             Dimension[] iconSizes = getIconSizesMap();
 943             return type >= 0 && type < iconSizes.length ?
 944                 iconSizes[type] : null;
 945         }
 946 
 947         /**
 948          * Change icon size in a type to size mapping. This is called by code
 949          * that parses the gtk-icon-sizes setting
 950          *
 951          * @param type icon type (GtkIconSize value)
 952          * @param w the new icon width
 953          * @param h the new icon height
 954          */
 955         public static void setIconSize(int type, int w, int h) {
 956             Dimension[] iconSizes = getIconSizesMap();
 957             if (type >= 0 && type < iconSizes.length) {
 958                 iconSizes[type] = new Dimension(w, h);
 959             }
 960         }
 961 
 962         /**
 963          * Return icon type (GtkIconSize value) given a symbolic name which can
 964          * occur in a theme file.
 965          *
 966          * @param size symbolic name, e.g. gtk-button
 967          * @return icon type. Valid types are 1 to 6
 968          */
 969         public static int getIconType(String size) {
 970             if (size == null) {
 971                 return UNDEFINED;
 972             }
 973             if (ICON_TYPE_MAP == null) {
 974                 initIconTypeMap();
 975             }
 976             Integer n = ICON_TYPE_MAP.get(size);
 977             return n != null ? n.intValue() : UNDEFINED;
 978         }
 979 
 980         private static void initIconTypeMap() {
 981             ICON_TYPE_MAP = new HashMap<String,Integer>();
 982             ICON_TYPE_MAP.put("gtk-menu", Integer.valueOf(1));
 983             ICON_TYPE_MAP.put("gtk-small-toolbar", Integer.valueOf(2));
 984             ICON_TYPE_MAP.put("gtk-large-toolbar", Integer.valueOf(3));
 985             ICON_TYPE_MAP.put("gtk-button", Integer.valueOf(4));
 986             ICON_TYPE_MAP.put("gtk-dnd", Integer.valueOf(5));
 987             ICON_TYPE_MAP.put("gtk-dialog", Integer.valueOf(6));
 988         }
 989 
 990     }
 991 
 992     /**
 993      * An Icon that is fetched using getStockIcon.
 994      */
 995     private static class GTKStockIcon implements SynthIcon {
 996         private String key;
 997         private int size;
 998         private boolean loadedLTR;
 999         private boolean loadedRTL;
1000         private Icon ltrIcon;
1001         private Icon rtlIcon;
1002         private SynthStyle style;
1003 
1004         GTKStockIcon(String key, int size) {
1005             this.key = key;
1006             this.size = size;
1007         }
1008 
1009         public void paintIcon(SynthContext context, Graphics g, int x,
1010                               int y, int w, int h) {
1011             Icon icon = getIcon(context);
1012 
1013             if (icon != null) {
1014                 if (context == null) {
1015                     icon.paintIcon(null, g, x, y);
1016                 }
1017                 else {
1018                     icon.paintIcon(context.getComponent(), g, x, y);
1019                 }
1020             }
1021         }
1022 
1023         public int getIconWidth(SynthContext context) {
1024             Icon icon = getIcon(context);
1025 
1026             if (icon != null) {
1027                 return icon.getIconWidth();
1028             }
1029             return 0;
1030         }
1031 
1032         public int getIconHeight(SynthContext context) {
1033             Icon icon = getIcon(context);
1034 
1035             if (icon != null) {
1036                 return icon.getIconHeight();
1037             }
1038             return 0;
1039         }
1040 
1041         private Icon getIcon(SynthContext context) {
1042             if (context != null) {
1043                 ComponentOrientation co = context.getComponent().
1044                                                   getComponentOrientation();
1045                 SynthStyle style = context.getStyle();
1046 
1047                 if (style != this.style) {
1048                     this.style = style;
1049                     loadedLTR = loadedRTL = false;
1050                 }
1051                 if (co == null || co.isLeftToRight()) {
1052                     if (!loadedLTR) {
1053                         loadedLTR = true;
1054                         ltrIcon = ((GTKStyle)context.getStyle()).
1055                                   getStockIcon(context, key, size);
1056                     }
1057                     return ltrIcon;
1058                 }
1059                 else if (!loadedRTL) {
1060                     loadedRTL = true;
1061                     rtlIcon = ((GTKStyle)context.getStyle()).
1062                               getStockIcon(context, key,size);
1063                 }
1064                 return rtlIcon;
1065             }
1066             return ltrIcon;
1067         }
1068     }
1069 
1070     /**
1071      * GTKLazyValue is a slimmed down version of <code>ProxyLaxyValue</code>.
1072      * The code is duplicate so that it can get at the package private
1073      * classes in gtk.
1074      */
1075     static class GTKLazyValue implements UIDefaults.LazyValue {
1076         /**
1077          * Name of the class to create.
1078          */
1079         private String className;
1080         private String methodName;
1081 
1082         GTKLazyValue(String name) {
1083             this(name, null);
1084         }
1085 
1086         GTKLazyValue(String name, String methodName) {
1087             this.className = name;
1088             this.methodName = methodName;
1089         }
1090 
1091         public Object createValue(UIDefaults table) {
1092             try {
1093                 Class<?> c = Class.forName(className, true,Thread.currentThread().
1094                                            getContextClassLoader());
1095 
1096                 if (methodName == null) {
1097                     @SuppressWarnings("deprecation")
1098                     Object result = c.newInstance();
1099                     return result;
1100                 }
1101                 Method m = c.getMethod(methodName, (Class<?>[])null);
1102 
1103                 return m.invoke(c, (Object[])null);
1104             } catch (ClassNotFoundException cnfe) {
1105             } catch (IllegalAccessException iae) {
1106             } catch (InvocationTargetException ite) {
1107             } catch (NoSuchMethodException nsme) {
1108             } catch (InstantiationException ie) {
1109             }
1110             return null;
1111         }
1112     }
1113 
1114     static {
1115         CLASS_SPECIFIC_MAP = new HashMap<String,String>();
1116         CLASS_SPECIFIC_MAP.put("Slider.thumbHeight", "slider-width");
1117         CLASS_SPECIFIC_MAP.put("Slider.trackBorder", "trough-border");
1118         CLASS_SPECIFIC_MAP.put("SplitPane.size", "handle-size");
1119         CLASS_SPECIFIC_MAP.put("Tree.expanderSize", "expander-size");
1120         CLASS_SPECIFIC_MAP.put("ScrollBar.thumbHeight", "slider-width");
1121         CLASS_SPECIFIC_MAP.put("ScrollBar.width", "slider-width");
1122         CLASS_SPECIFIC_MAP.put("TextArea.caretForeground", "cursor-color");
1123         CLASS_SPECIFIC_MAP.put("TextArea.caretAspectRatio", "cursor-aspect-ratio");
1124         CLASS_SPECIFIC_MAP.put("TextField.caretForeground", "cursor-color");
1125         CLASS_SPECIFIC_MAP.put("TextField.caretAspectRatio", "cursor-aspect-ratio");
1126         CLASS_SPECIFIC_MAP.put("PasswordField.caretForeground", "cursor-color");
1127         CLASS_SPECIFIC_MAP.put("PasswordField.caretAspectRatio", "cursor-aspect-ratio");
1128         CLASS_SPECIFIC_MAP.put("FormattedTextField.caretForeground", "cursor-color");
1129         CLASS_SPECIFIC_MAP.put("FormattedTextField.caretAspectRatio", "cursor-aspect-");
1130         CLASS_SPECIFIC_MAP.put("TextPane.caretForeground", "cursor-color");
1131         CLASS_SPECIFIC_MAP.put("TextPane.caretAspectRatio", "cursor-aspect-ratio");
1132         CLASS_SPECIFIC_MAP.put("EditorPane.caretForeground", "cursor-color");
1133         CLASS_SPECIFIC_MAP.put("EditorPane.caretAspectRatio", "cursor-aspect-ratio");
1134 
1135         ICONS_MAP = new HashMap<String, GTKStockIcon>();
1136         ICONS_MAP.put("FileChooser.cancelIcon", new GTKStockIcon("gtk-cancel", 4));
1137         ICONS_MAP.put("FileChooser.okIcon",     new GTKStockIcon("gtk-ok",     4));
1138         ICONS_MAP.put("OptionPane.errorIcon", new GTKStockIcon("gtk-dialog-error", 6));
1139         ICONS_MAP.put("OptionPane.informationIcon", new GTKStockIcon("gtk-dialog-info", 6));
1140         ICONS_MAP.put("OptionPane.warningIcon", new GTKStockIcon("gtk-dialog-warning", 6));
1141         ICONS_MAP.put("OptionPane.questionIcon", new GTKStockIcon("gtk-dialog-question", 6));
1142         ICONS_MAP.put("OptionPane.yesIcon", new GTKStockIcon("gtk-yes", 4));
1143         ICONS_MAP.put("OptionPane.noIcon", new GTKStockIcon("gtk-no", 4));
1144         ICONS_MAP.put("OptionPane.cancelIcon", new GTKStockIcon("gtk-cancel", 4));
1145         ICONS_MAP.put("OptionPane.okIcon", new GTKStockIcon("gtk-ok", 4));
1146     }
1147 }