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