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