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