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 }