J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 2002-2006 Sun Microsystems, Inc. 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. Sun designates this |
| 8 | * particular file as subject to the "Classpath" exception as provided |
| 9 | * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
| 22 | * CA 95054 USA or visit www.sun.com if you need additional information or |
| 23 | * have any questions. |
| 24 | */ |
| 25 | package javax.swing.plaf.synth; |
| 26 | |
| 27 | import java.awt.*; |
| 28 | import java.beans.*; |
| 29 | import java.io.*; |
| 30 | import java.lang.ref.*; |
| 31 | import java.net.*; |
| 32 | import java.security.*; |
| 33 | import java.text.*; |
| 34 | import java.util.*; |
| 35 | import javax.swing.*; |
| 36 | import javax.swing.plaf.*; |
| 37 | import javax.swing.plaf.basic.*; |
| 38 | |
| 39 | import sun.awt.*; |
| 40 | import sun.security.action.*; |
| 41 | import sun.swing.*; |
| 42 | import sun.swing.plaf.synth.*; |
| 43 | |
| 44 | /** |
| 45 | * SynthLookAndFeel provides the basis for creating a customized look and |
| 46 | * feel. SynthLookAndFeel does not directly provide a look, all painting is |
| 47 | * delegated. |
| 48 | * You need to either provide a configuration file, by way of the |
| 49 | * {@link #load} method, or provide your own {@link SynthStyleFactory} |
| 50 | * to {@link #setStyleFactory}. Refer to the |
| 51 | * <a href="package-summary.html">package summary</a> for an example of |
| 52 | * loading a file, and {@link javax.swing.plaf.synth.SynthStyleFactory} for |
| 53 | * an example of providing your own <code>SynthStyleFactory</code> to |
| 54 | * <code>setStyleFactory</code>. |
| 55 | * <p> |
| 56 | * <strong>Warning:</strong> |
| 57 | * This class implements {@link Serializable} as a side effect of it |
| 58 | * extending {@link BasicLookAndFeel}. It is not intended to be serialized. |
| 59 | * An attempt to serialize it will |
| 60 | * result in {@link NotSerializableException}. |
| 61 | * |
| 62 | * @serial exclude |
| 63 | * @since 1.5 |
| 64 | * @author Scott Violet |
| 65 | */ |
| 66 | public class SynthLookAndFeel extends BasicLookAndFeel { |
| 67 | /** |
| 68 | * Used in a handful of places where we need an empty Insets. |
| 69 | */ |
| 70 | static final Insets EMPTY_UIRESOURCE_INSETS = new InsetsUIResource( |
| 71 | 0, 0, 0, 0); |
| 72 | |
| 73 | /** |
| 74 | * AppContext key to get the current SynthStyleFactory. |
| 75 | */ |
| 76 | private static final Object STYLE_FACTORY_KEY = |
| 77 | new StringBuffer("com.sun.java.swing.plaf.gtk.StyleCache"); |
| 78 | |
| 79 | /** |
| 80 | * The last SynthStyleFactory that was asked for from AppContext |
| 81 | * <code>lastContext</code>. |
| 82 | */ |
| 83 | private static SynthStyleFactory lastFactory; |
| 84 | /** |
| 85 | * If this is true it indicates there is more than one AppContext active |
| 86 | * and that we need to make sure in getStyleCache the requesting |
| 87 | * AppContext matches that of <code>lastContext</code> before returning |
| 88 | * it. |
| 89 | */ |
| 90 | private static boolean multipleApps; |
| 91 | /** |
| 92 | * AppContext lastLAF came from. |
| 93 | */ |
| 94 | private static AppContext lastContext; |
| 95 | |
| 96 | // Refer to setSelectedUI |
| 97 | static ComponentUI selectedUI; |
| 98 | // Refer to setSelectedUI |
| 99 | static int selectedUIState; |
| 100 | |
| 101 | /** |
| 102 | * SynthStyleFactory for the this SynthLookAndFeel. |
| 103 | */ |
| 104 | private SynthStyleFactory factory; |
| 105 | |
| 106 | /** |
| 107 | * Map of defaults table entries. This is populated via the load |
| 108 | * method. |
| 109 | */ |
| 110 | private Map defaultsMap; |
| 111 | |
| 112 | private Handler _handler; |
| 113 | |
| 114 | /** |
| 115 | * Used by the renderers. For the most part the renderers are implemented |
| 116 | * as Labels, which is problematic in so far as they are never selected. |
| 117 | * To accomodate this SynthLabelUI checks if the current |
| 118 | * UI matches that of <code>selectedUI</code> (which this methods sets), if |
| 119 | * it does, then a state as set by this method is returned. This provides |
| 120 | * a way for labels to have a state other than selected. |
| 121 | */ |
| 122 | static void setSelectedUI(ComponentUI uix, boolean selected, |
| 123 | boolean focused, boolean enabled, |
| 124 | boolean rollover) { |
| 125 | selectedUI = uix; |
| 126 | selectedUIState = 0; |
| 127 | if (selected) { |
| 128 | selectedUIState = SynthConstants.SELECTED; |
| 129 | if (focused) { |
| 130 | selectedUIState |= SynthConstants.FOCUSED; |
| 131 | } |
| 132 | } |
| 133 | else if (rollover && enabled) { |
| 134 | selectedUIState |= |
| 135 | SynthConstants.MOUSE_OVER | SynthConstants.ENABLED; |
| 136 | if (focused) { |
| 137 | selectedUIState |= SynthConstants.FOCUSED; |
| 138 | } |
| 139 | } |
| 140 | else { |
| 141 | selectedUIState = SynthConstants.FOCUSED; |
| 142 | if (enabled) { |
| 143 | selectedUIState |= SynthConstants.ENABLED; |
| 144 | } |
| 145 | else { |
| 146 | selectedUIState |= SynthConstants.DISABLED; |
| 147 | } |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | /** |
| 152 | * Clears out the selected UI that was last set in setSelectedUI. |
| 153 | */ |
| 154 | static void resetSelectedUI() { |
| 155 | selectedUI = null; |
| 156 | } |
| 157 | |
| 158 | |
| 159 | /** |
| 160 | * Sets the SynthStyleFactory that the UI classes provided by |
| 161 | * synth will use to obtain a SynthStyle. |
| 162 | * |
| 163 | * @param cache SynthStyleFactory the UIs should use. |
| 164 | */ |
| 165 | public static void setStyleFactory(SynthStyleFactory cache) { |
| 166 | // We assume the setter is called BEFORE the getter has been invoked |
| 167 | // for a particular AppContext. |
| 168 | synchronized(SynthLookAndFeel.class) { |
| 169 | AppContext context = AppContext.getAppContext(); |
| 170 | if (!multipleApps && context != lastContext && |
| 171 | lastContext != null) { |
| 172 | multipleApps = true; |
| 173 | } |
| 174 | lastFactory = cache; |
| 175 | lastContext = context; |
| 176 | context.put(STYLE_FACTORY_KEY, cache); |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | /** |
| 181 | * Returns the current SynthStyleFactory. |
| 182 | * |
| 183 | * @return SynthStyleFactory |
| 184 | */ |
| 185 | public static SynthStyleFactory getStyleFactory() { |
| 186 | synchronized(SynthLookAndFeel.class) { |
| 187 | if (!multipleApps) { |
| 188 | return lastFactory; |
| 189 | } |
| 190 | AppContext context = AppContext.getAppContext(); |
| 191 | |
| 192 | if (lastContext == context) { |
| 193 | return lastFactory; |
| 194 | } |
| 195 | lastContext = context; |
| 196 | lastFactory = (SynthStyleFactory)AppContext.getAppContext().get |
| 197 | (STYLE_FACTORY_KEY); |
| 198 | return lastFactory; |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | /** |
| 203 | * Returns the component state for the specified component. This should |
| 204 | * only be used for Components that don't have any special state beyond |
| 205 | * that of ENABLED, DISABLED or FOCUSED. For example, buttons shouldn't |
| 206 | * call into this method. |
| 207 | */ |
| 208 | static int getComponentState(Component c) { |
| 209 | if (c.isEnabled()) { |
| 210 | if (c.isFocusOwner()) { |
| 211 | return SynthUI.ENABLED | SynthUI.FOCUSED; |
| 212 | } |
| 213 | return SynthUI.ENABLED; |
| 214 | } |
| 215 | return SynthUI.DISABLED; |
| 216 | } |
| 217 | |
| 218 | /** |
| 219 | * Gets a SynthStyle for the specified region of the specified component. |
| 220 | * This is not for general consumption, only custom UIs should call this |
| 221 | * method. |
| 222 | * |
| 223 | * @param c JComponent to get the SynthStyle for |
| 224 | * @param region Identifies the region of the specified component |
| 225 | * @return SynthStyle to use. |
| 226 | */ |
| 227 | public static SynthStyle getStyle(JComponent c, Region region) { |
| 228 | return getStyleFactory().getStyle(c, region); |
| 229 | } |
| 230 | |
| 231 | /** |
| 232 | * Returns true if the Style should be updated in response to the |
| 233 | * specified PropertyChangeEvent. This forwards to |
| 234 | * <code>shouldUpdateStyleOnAncestorChanged</code> as necessary. |
| 235 | */ |
| 236 | static boolean shouldUpdateStyle(PropertyChangeEvent event) { |
| 237 | String eName = event.getPropertyName(); |
| 238 | if ("name" == eName) { |
| 239 | // Always update on a name change |
| 240 | return true; |
| 241 | } |
| 242 | else if ("componentOrientation" == eName) { |
| 243 | // Always update on a component orientation change |
| 244 | return true; |
| 245 | } |
| 246 | else if ("ancestor" == eName && event.getNewValue() != null) { |
| 247 | // Only update on an ancestor change when getting a valid |
| 248 | // parent and the LookAndFeel wants this. |
| 249 | LookAndFeel laf = UIManager.getLookAndFeel(); |
| 250 | return (laf instanceof SynthLookAndFeel && |
| 251 | ((SynthLookAndFeel)laf). |
| 252 | shouldUpdateStyleOnAncestorChanged()); |
| 253 | } |
| 254 | return false; |
| 255 | } |
| 256 | |
| 257 | /** |
| 258 | * A convience method that will reset the Style of StyleContext if |
| 259 | * necessary. |
| 260 | * |
| 261 | * @return newStyle |
| 262 | */ |
| 263 | static SynthStyle updateStyle(SynthContext context, SynthUI ui) { |
| 264 | SynthStyle newStyle = getStyle(context.getComponent(), |
| 265 | context.getRegion()); |
| 266 | SynthStyle oldStyle = context.getStyle(); |
| 267 | |
| 268 | if (newStyle != oldStyle) { |
| 269 | if (oldStyle != null) { |
| 270 | oldStyle.uninstallDefaults(context); |
| 271 | } |
| 272 | context.setStyle(newStyle); |
| 273 | newStyle.installDefaults(context, ui); |
| 274 | } |
| 275 | return newStyle; |
| 276 | } |
| 277 | |
| 278 | /** |
| 279 | * Updates the style associated with <code>c</code>, and all its children. |
| 280 | * This is a lighter version of |
| 281 | * <code>SwingUtilities.updateComponentTreeUI</code>. |
| 282 | * |
| 283 | * @param c Component to update style for. |
| 284 | */ |
| 285 | public static void updateStyles(Component c) { |
| 286 | _updateStyles(c); |
| 287 | c.repaint(); |
| 288 | } |
| 289 | |
| 290 | // Implementation for updateStyles |
| 291 | private static void _updateStyles(Component c) { |
| 292 | if (c instanceof JComponent) { |
| 293 | // Yes, this is hacky. A better solution is to get the UI |
| 294 | // and cast, but JComponent doesn't expose a getter for the UI |
| 295 | // (each of the UIs do), making that approach impractical. |
| 296 | String name = c.getName(); |
| 297 | c.setName(null); |
| 298 | if (name != null) { |
| 299 | c.setName(name); |
| 300 | } |
| 301 | ((JComponent)c).revalidate(); |
| 302 | } |
| 303 | Component[] children = null; |
| 304 | if (c instanceof JMenu) { |
| 305 | children = ((JMenu)c).getMenuComponents(); |
| 306 | } |
| 307 | else if (c instanceof Container) { |
| 308 | children = ((Container)c).getComponents(); |
| 309 | } |
| 310 | if (children != null) { |
| 311 | for(int i = 0; i < children.length; i++) { |
| 312 | updateStyles(children[i]); |
| 313 | } |
| 314 | } |
| 315 | } |
| 316 | |
| 317 | /** |
| 318 | * Returns the Region for the JComponent <code>c</code>. |
| 319 | * |
| 320 | * @param c JComponent to fetch the Region for |
| 321 | * @return Region corresponding to <code>c</code> |
| 322 | */ |
| 323 | public static Region getRegion(JComponent c) { |
| 324 | return Region.getRegion(c); |
| 325 | } |
| 326 | |
| 327 | /** |
| 328 | * A convenience method to return where the foreground should be |
| 329 | * painted for the Component identified by the passed in |
| 330 | * AbstractSynthContext. |
| 331 | */ |
| 332 | static Insets getPaintingInsets(SynthContext state, Insets insets) { |
| 333 | if (state.isSubregion()) { |
| 334 | insets = state.getStyle().getInsets(state, insets); |
| 335 | } |
| 336 | else { |
| 337 | insets = state.getComponent().getInsets(insets); |
| 338 | } |
| 339 | return insets; |
| 340 | } |
| 341 | |
| 342 | /** |
| 343 | * A convenience method that handles painting of the background. |
| 344 | * All SynthUI implementations should override update and invoke |
| 345 | * this method. |
| 346 | */ |
| 347 | static void update(SynthContext state, Graphics g) { |
| 348 | paintRegion(state, g, null); |
| 349 | } |
| 350 | |
| 351 | /** |
| 352 | * A convenience method that handles painting of the background for |
| 353 | * subregions. All SynthUI's that have subregions should invoke |
| 354 | * this method, than paint the foreground. |
| 355 | */ |
| 356 | static void updateSubregion(SynthContext state, Graphics g, |
| 357 | Rectangle bounds) { |
| 358 | paintRegion(state, g, bounds); |
| 359 | } |
| 360 | |
| 361 | private static void paintRegion(SynthContext state, Graphics g, |
| 362 | Rectangle bounds) { |
| 363 | JComponent c = state.getComponent(); |
| 364 | SynthStyle style = state.getStyle(); |
| 365 | int x, y, width, height; |
| 366 | |
| 367 | if (bounds == null) { |
| 368 | x = 0; |
| 369 | y = 0; |
| 370 | width = c.getWidth(); |
| 371 | height = c.getHeight(); |
| 372 | } |
| 373 | else { |
| 374 | x = bounds.x; |
| 375 | y = bounds.y; |
| 376 | width = bounds.width; |
| 377 | height = bounds.height; |
| 378 | } |
| 379 | |
| 380 | // Fill in the background, if necessary. |
| 381 | boolean subregion = state.isSubregion(); |
| 382 | if ((subregion && style.isOpaque(state)) || |
| 383 | (!subregion && c.isOpaque())) { |
| 384 | g.setColor(style.getColor(state, ColorType.BACKGROUND)); |
| 385 | g.fillRect(x, y, width, height); |
| 386 | } |
| 387 | } |
| 388 | |
| 389 | static boolean isLeftToRight(Component c) { |
| 390 | return c.getComponentOrientation().isLeftToRight(); |
| 391 | } |
| 392 | |
| 393 | /** |
| 394 | * Returns the ui that is of type <code>klass</code>, or null if |
| 395 | * one can not be found. |
| 396 | */ |
| 397 | static Object getUIOfType(ComponentUI ui, Class klass) { |
| 398 | if (klass.isInstance(ui)) { |
| 399 | return ui; |
| 400 | } |
| 401 | return null; |
| 402 | } |
| 403 | |
| 404 | /** |
| 405 | * Creates the Synth look and feel <code>ComponentUI</code> for |
| 406 | * the passed in <code>JComponent</code>. |
| 407 | * |
| 408 | * @param c JComponent to create the <code>ComponentUI</code> for |
| 409 | * @return ComponentUI to use for <code>c</code> |
| 410 | */ |
| 411 | public static ComponentUI createUI(JComponent c) { |
| 412 | String key = c.getUIClassID().intern(); |
| 413 | |
| 414 | if (key == "ButtonUI") { |
| 415 | return SynthButtonUI.createUI(c); |
| 416 | } |
| 417 | else if (key == "CheckBoxUI") { |
| 418 | return SynthCheckBoxUI.createUI(c); |
| 419 | } |
| 420 | else if (key == "CheckBoxMenuItemUI") { |
| 421 | return SynthCheckBoxMenuItemUI.createUI(c); |
| 422 | } |
| 423 | else if (key == "ColorChooserUI") { |
| 424 | return SynthColorChooserUI.createUI(c); |
| 425 | } |
| 426 | else if (key == "ComboBoxUI") { |
| 427 | return SynthComboBoxUI.createUI(c); |
| 428 | } |
| 429 | else if (key == "DesktopPaneUI") { |
| 430 | return SynthDesktopPaneUI.createUI(c); |
| 431 | } |
| 432 | else if (key == "DesktopIconUI") { |
| 433 | return SynthDesktopIconUI.createUI(c); |
| 434 | } |
| 435 | else if (key == "EditorPaneUI") { |
| 436 | return SynthEditorPaneUI.createUI(c); |
| 437 | } |
| 438 | else if (key == "FileChooserUI") { |
| 439 | return SynthFileChooserUI.createUI(c); |
| 440 | } |
| 441 | else if (key == "FormattedTextFieldUI") { |
| 442 | return SynthFormattedTextFieldUI.createUI(c); |
| 443 | } |
| 444 | else if (key == "InternalFrameUI") { |
| 445 | return SynthInternalFrameUI.createUI(c); |
| 446 | } |
| 447 | else if (key == "LabelUI") { |
| 448 | return SynthLabelUI.createUI(c); |
| 449 | } |
| 450 | else if (key == "ListUI") { |
| 451 | return SynthListUI.createUI(c); |
| 452 | } |
| 453 | else if (key == "MenuBarUI") { |
| 454 | return SynthMenuBarUI.createUI(c); |
| 455 | } |
| 456 | else if (key == "MenuUI") { |
| 457 | return SynthMenuUI.createUI(c); |
| 458 | } |
| 459 | else if (key == "MenuItemUI") { |
| 460 | return SynthMenuItemUI.createUI(c); |
| 461 | } |
| 462 | else if (key == "OptionPaneUI") { |
| 463 | return SynthOptionPaneUI.createUI(c); |
| 464 | } |
| 465 | else if (key == "PanelUI") { |
| 466 | return SynthPanelUI.createUI(c); |
| 467 | } |
| 468 | else if (key == "PasswordFieldUI") { |
| 469 | return SynthPasswordFieldUI.createUI(c); |
| 470 | } |
| 471 | else if (key == "PopupMenuSeparatorUI") { |
| 472 | return SynthSeparatorUI.createUI(c); |
| 473 | } |
| 474 | else if (key == "PopupMenuUI") { |
| 475 | return SynthPopupMenuUI.createUI(c); |
| 476 | } |
| 477 | else if (key == "ProgressBarUI") { |
| 478 | return SynthProgressBarUI.createUI(c); |
| 479 | } |
| 480 | else if (key == "RadioButtonUI") { |
| 481 | return SynthRadioButtonUI.createUI(c); |
| 482 | } |
| 483 | else if (key == "RadioButtonMenuItemUI") { |
| 484 | return SynthRadioButtonMenuItemUI.createUI(c); |
| 485 | } |
| 486 | else if (key == "RootPaneUI") { |
| 487 | return SynthRootPaneUI.createUI(c); |
| 488 | } |
| 489 | else if (key == "ScrollBarUI") { |
| 490 | return SynthScrollBarUI.createUI(c); |
| 491 | } |
| 492 | else if (key == "ScrollPaneUI") { |
| 493 | return SynthScrollPaneUI.createUI(c); |
| 494 | } |
| 495 | else if (key == "SeparatorUI") { |
| 496 | return SynthSeparatorUI.createUI(c); |
| 497 | } |
| 498 | else if (key == "SliderUI") { |
| 499 | return SynthSliderUI.createUI(c); |
| 500 | } |
| 501 | else if (key == "SpinnerUI") { |
| 502 | return SynthSpinnerUI.createUI(c); |
| 503 | } |
| 504 | else if (key == "SplitPaneUI") { |
| 505 | return SynthSplitPaneUI.createUI(c); |
| 506 | } |
| 507 | else if (key == "TabbedPaneUI") { |
| 508 | return SynthTabbedPaneUI.createUI(c); |
| 509 | } |
| 510 | else if (key == "TableUI") { |
| 511 | return SynthTableUI.createUI(c); |
| 512 | } |
| 513 | else if (key == "TableHeaderUI") { |
| 514 | return SynthTableHeaderUI.createUI(c); |
| 515 | } |
| 516 | else if (key == "TextAreaUI") { |
| 517 | return SynthTextAreaUI.createUI(c); |
| 518 | } |
| 519 | else if (key == "TextFieldUI") { |
| 520 | return SynthTextFieldUI.createUI(c); |
| 521 | } |
| 522 | else if (key == "TextPaneUI") { |
| 523 | return SynthTextPaneUI.createUI(c); |
| 524 | } |
| 525 | else if (key == "ToggleButtonUI") { |
| 526 | return SynthToggleButtonUI.createUI(c); |
| 527 | } |
| 528 | else if (key == "ToolBarSeparatorUI") { |
| 529 | return SynthSeparatorUI.createUI(c); |
| 530 | } |
| 531 | else if (key == "ToolBarUI") { |
| 532 | return SynthToolBarUI.createUI(c); |
| 533 | } |
| 534 | else if (key == "ToolTipUI") { |
| 535 | return SynthToolTipUI.createUI(c); |
| 536 | } |
| 537 | else if (key == "TreeUI") { |
| 538 | return SynthTreeUI.createUI(c); |
| 539 | } |
| 540 | else if (key == "ViewportUI") { |
| 541 | return SynthViewportUI.createUI(c); |
| 542 | } |
| 543 | return null; |
| 544 | } |
| 545 | |
| 546 | |
| 547 | /** |
| 548 | * Creates a SynthLookAndFeel. |
| 549 | * <p> |
| 550 | * For the returned <code>SynthLookAndFeel</code> to be useful you need to |
| 551 | * invoke <code>load</code> to specify the set of |
| 552 | * <code>SynthStyle</code>s, or invoke <code>setStyleFactory</code>. |
| 553 | * |
| 554 | * @see #load |
| 555 | * @see #setStyleFactory |
| 556 | */ |
| 557 | public SynthLookAndFeel() { |
| 558 | factory = new DefaultSynthStyleFactory(); |
| 559 | _handler = new Handler(); |
| 560 | } |
| 561 | |
| 562 | /** |
| 563 | * Loads the set of <code>SynthStyle</code>s that will be used by |
| 564 | * this <code>SynthLookAndFeel</code>. <code>resourceBase</code> is |
| 565 | * used to resolve any path based resources, for example an |
| 566 | * <code>Image</code> would be resolved by |
| 567 | * <code>resourceBase.getResource(path)</code>. Refer to |
| 568 | * <a href="doc-files/synthFileFormat.html">Synth File Format</a> |
| 569 | * for more information. |
| 570 | * |
| 571 | * @param input InputStream to load from |
| 572 | * @param resourceBase used to resolve any images or other resources |
| 573 | * @throws ParseException if there is an error in parsing |
| 574 | * @throws IllegalArgumentException if input or resourceBase is <code>null</code> |
| 575 | */ |
| 576 | public void load(InputStream input, Class<?> resourceBase) throws |
| 577 | ParseException { |
| 578 | if (resourceBase == null) { |
| 579 | throw new IllegalArgumentException( |
| 580 | "You must supply a valid resource base Class"); |
| 581 | } |
| 582 | |
| 583 | if (defaultsMap == null) { |
| 584 | defaultsMap = new HashMap(); |
| 585 | } |
| 586 | |
| 587 | new SynthParser().parse(input, (DefaultSynthStyleFactory) factory, |
| 588 | null, resourceBase, defaultsMap); |
| 589 | } |
| 590 | |
| 591 | /** |
| 592 | * Loads the set of <code>SynthStyle</code>s that will be used by |
| 593 | * this <code>SynthLookAndFeel</code>. Path based resources are resolved |
| 594 | * relatively to the specified <code>URL</code> of the style. For example |
| 595 | * an <code>Image</code> would be resolved by |
| 596 | * <code>new URL(synthFile, path)</code>. Refer to |
| 597 | * <a href="doc-files/synthFileFormat.html">Synth File Format</a> for more |
| 598 | * information. |
| 599 | * |
| 600 | * @param url the <code>URL</code> to load the set of |
| 601 | * <code>SynthStyle</code> from |
| 602 | * @throws ParseException if there is an error in parsing |
| 603 | * @throws IllegalArgumentException if synthSet is <code>null</code> |
| 604 | * @throws IOException if synthSet cannot be opened as an <code>InputStream</code> |
| 605 | * @since 1.6 |
| 606 | */ |
| 607 | public void load(URL url) throws ParseException, IOException { |
| 608 | if (url == null) { |
| 609 | throw new IllegalArgumentException( |
| 610 | "You must supply a valid Synth set URL"); |
| 611 | } |
| 612 | |
| 613 | if (defaultsMap == null) { |
| 614 | defaultsMap = new HashMap(); |
| 615 | } |
| 616 | |
| 617 | InputStream input = url.openStream(); |
| 618 | new SynthParser().parse(input, (DefaultSynthStyleFactory) factory, |
| 619 | url, null, defaultsMap); |
| 620 | } |
| 621 | |
| 622 | /** |
| 623 | * Called by UIManager when this look and feel is installed. |
| 624 | */ |
| 625 | public void initialize() { |
| 626 | super.initialize(); |
| 627 | DefaultLookup.setDefaultLookup(new SynthDefaultLookup()); |
| 628 | setStyleFactory(factory); |
| 629 | KeyboardFocusManager.getCurrentKeyboardFocusManager(). |
| 630 | addPropertyChangeListener(_handler); |
| 631 | } |
| 632 | |
| 633 | /** |
| 634 | * Called by UIManager when this look and feel is uninstalled. |
| 635 | */ |
| 636 | public void uninitialize() { |
| 637 | KeyboardFocusManager.getCurrentKeyboardFocusManager(). |
| 638 | removePropertyChangeListener(_handler); |
| 639 | // We should uninstall the StyleFactory here, but unfortunately |
| 640 | // there are a handful of things that retain references to the |
| 641 | // LookAndFeel and expect things to work |
| 642 | super.uninitialize(); |
| 643 | } |
| 644 | |
| 645 | /** |
| 646 | * Returns the defaults for this SynthLookAndFeel. |
| 647 | * |
| 648 | * @return Defaults table. |
| 649 | */ |
| 650 | public UIDefaults getDefaults() { |
| 651 | UIDefaults table = new UIDefaults(60, 0.75f); |
| 652 | |
| 653 | Region.registerUIs(table); |
| 654 | table.setDefaultLocale(Locale.getDefault()); |
| 655 | table.addResourceBundle( |
| 656 | "com.sun.swing.internal.plaf.basic.resources.basic" ); |
| 657 | table.addResourceBundle("com.sun.swing.internal.plaf.synth.resources.synth"); |
| 658 | |
| 659 | // SynthTabbedPaneUI supports rollover on tabs, GTK does not |
| 660 | table.put("TabbedPane.isTabRollover", Boolean.TRUE); |
| 661 | |
| 662 | // These need to be defined for JColorChooser to work. |
| 663 | table.put("ColorChooser.swatchesRecentSwatchSize", |
| 664 | new Dimension(10, 10)); |
| 665 | table.put("ColorChooser.swatchesDefaultRecentColor", Color.RED); |
| 666 | table.put("ColorChooser.swatchesSwatchSize", new Dimension(10, 10)); |
| 667 | |
| 668 | // These are needed for PopupMenu. |
| 669 | table.put("PopupMenu.selectedWindowInputMapBindings", new Object[] { |
| 670 | "ESCAPE", "cancel", |
| 671 | "DOWN", "selectNext", |
| 672 | "KP_DOWN", "selectNext", |
| 673 | "UP", "selectPrevious", |
| 674 | "KP_UP", "selectPrevious", |
| 675 | "LEFT", "selectParent", |
| 676 | "KP_LEFT", "selectParent", |
| 677 | "RIGHT", "selectChild", |
| 678 | "KP_RIGHT", "selectChild", |
| 679 | "ENTER", "return", |
| 680 | "SPACE", "return" |
| 681 | }); |
| 682 | table.put("PopupMenu.selectedWindowInputMapBindings.RightToLeft", |
| 683 | new Object[] { |
| 684 | "LEFT", "selectChild", |
| 685 | "KP_LEFT", "selectChild", |
| 686 | "RIGHT", "selectParent", |
| 687 | "KP_RIGHT", "selectParent", |
| 688 | }); |
| 689 | |
| 690 | // enabled antialiasing depending on desktop settings |
| 691 | flushUnreferenced(); |
| 692 | Object aaTextInfo = getAATextInfo(); |
| 693 | table.put(SwingUtilities2.AA_TEXT_PROPERTY_KEY, aaTextInfo); |
| 694 | new AATextListener(this); |
| 695 | |
| 696 | if (defaultsMap != null) { |
| 697 | table.putAll(defaultsMap); |
| 698 | } |
| 699 | return table; |
| 700 | } |
| 701 | |
| 702 | /** |
| 703 | * Returns true, SynthLookAndFeel is always supported. |
| 704 | * |
| 705 | * @return true. |
| 706 | */ |
| 707 | public boolean isSupportedLookAndFeel() { |
| 708 | return true; |
| 709 | } |
| 710 | |
| 711 | /** |
| 712 | * Returns false, SynthLookAndFeel is not a native look and feel. |
| 713 | * |
| 714 | * @return false |
| 715 | */ |
| 716 | public boolean isNativeLookAndFeel() { |
| 717 | return false; |
| 718 | } |
| 719 | |
| 720 | /** |
| 721 | * Returns a textual description of SynthLookAndFeel. |
| 722 | * |
| 723 | * @return textual description of synth. |
| 724 | */ |
| 725 | public String getDescription() { |
| 726 | return "Synth look and feel"; |
| 727 | } |
| 728 | |
| 729 | /** |
| 730 | * Return a short string that identifies this look and feel. |
| 731 | * |
| 732 | * @return a short string identifying this look and feel. |
| 733 | */ |
| 734 | public String getName() { |
| 735 | return "Synth look and feel"; |
| 736 | } |
| 737 | |
| 738 | /** |
| 739 | * Return a string that identifies this look and feel. |
| 740 | * |
| 741 | * @return a short string identifying this look and feel. |
| 742 | */ |
| 743 | public String getID() { |
| 744 | return "Synth"; |
| 745 | } |
| 746 | |
| 747 | /** |
| 748 | * Returns whether or not the UIs should update their |
| 749 | * <code>SynthStyles</code> from the <code>SynthStyleFactory</code> |
| 750 | * when the ancestor of the <code>JComponent</code> changes. A subclass |
| 751 | * that provided a <code>SynthStyleFactory</code> that based the |
| 752 | * return value from <code>getStyle</code> off the containment hierarchy |
| 753 | * would override this method to return true. |
| 754 | * |
| 755 | * @return whether or not the UIs should update their |
| 756 | * <code>SynthStyles</code> from the <code>SynthStyleFactory</code> |
| 757 | * when the ancestor changed. |
| 758 | */ |
| 759 | public boolean shouldUpdateStyleOnAncestorChanged() { |
| 760 | return false; |
| 761 | } |
| 762 | |
| 763 | /** |
| 764 | * Returns the antialiasing information as specified by the host desktop. |
| 765 | * Antialiasing might be forced off if the desktop is GNOME and the user |
| 766 | * has set his locale to Chinese, Japanese or Korean. This is consistent |
| 767 | * with what GTK does. See com.sun.java.swing.plaf.gtk.GtkLookAndFeel |
| 768 | * for more information about CJK and antialiased fonts. |
| 769 | * |
| 770 | * @return the text antialiasing information associated to the desktop |
| 771 | */ |
| 772 | private static Object getAATextInfo() { |
| 773 | String language = Locale.getDefault().getLanguage(); |
| 774 | String desktop = (String) |
| 775 | AccessController.doPrivileged(new GetPropertyAction("sun.desktop")); |
| 776 | |
| 777 | boolean isCjkLocale = (Locale.CHINESE.getLanguage().equals(language) || |
| 778 | Locale.JAPANESE.getLanguage().equals(language) || |
| 779 | Locale.KOREAN.getLanguage().equals(language)); |
| 780 | boolean isGnome = "gnome".equals(desktop); |
| 781 | boolean isLocal = SwingUtilities2.isLocalDisplay(); |
| 782 | |
| 783 | boolean setAA = isLocal && (!isGnome || !isCjkLocale); |
| 784 | |
| 785 | Object aaTextInfo = SwingUtilities2.AATextInfo.getAATextInfo(setAA); |
| 786 | return aaTextInfo; |
| 787 | } |
| 788 | |
| 789 | private static ReferenceQueue queue = new ReferenceQueue(); |
| 790 | |
| 791 | private static void flushUnreferenced() { |
| 792 | AATextListener aatl; |
| 793 | while ((aatl = (AATextListener) queue.poll()) != null) { |
| 794 | aatl.dispose(); |
| 795 | } |
| 796 | } |
| 797 | |
| 798 | private static class AATextListener |
| 799 | extends WeakReference implements PropertyChangeListener { |
| 800 | private String key = SunToolkit.DESKTOPFONTHINTS; |
| 801 | |
| 802 | AATextListener(LookAndFeel laf) { |
| 803 | super(laf, queue); |
| 804 | Toolkit tk = Toolkit.getDefaultToolkit(); |
| 805 | tk.addPropertyChangeListener(key, this); |
| 806 | } |
| 807 | |
| 808 | public void propertyChange(PropertyChangeEvent pce) { |
| 809 | UIDefaults defaults = UIManager.getLookAndFeelDefaults(); |
| 810 | if (defaults.getBoolean("Synth.doNotSetTextAA")) { |
| 811 | dispose(); |
| 812 | return; |
| 813 | } |
| 814 | |
| 815 | LookAndFeel laf = (LookAndFeel) get(); |
| 816 | if (laf == null || laf != UIManager.getLookAndFeel()) { |
| 817 | dispose(); |
| 818 | return; |
| 819 | } |
| 820 | |
| 821 | Object aaTextInfo = getAATextInfo(); |
| 822 | defaults.put(SwingUtilities2.AA_TEXT_PROPERTY_KEY, aaTextInfo); |
| 823 | |
| 824 | updateUI(); |
| 825 | } |
| 826 | |
| 827 | void dispose() { |
| 828 | Toolkit tk = Toolkit.getDefaultToolkit(); |
| 829 | tk.removePropertyChangeListener(key, this); |
| 830 | } |
| 831 | |
| 832 | /** |
| 833 | * Updates the UI of the passed in window and all its children. |
| 834 | */ |
| 835 | private static void updateWindowUI(Window window) { |
| 836 | updateStyles(window); |
| 837 | Window ownedWins[] = window.getOwnedWindows(); |
| 838 | for (int i = 0; i < ownedWins.length; i++) { |
| 839 | updateWindowUI(ownedWins[i]); |
| 840 | } |
| 841 | } |
| 842 | |
| 843 | /** |
| 844 | * Updates the UIs of all the known Frames. |
| 845 | */ |
| 846 | private static void updateAllUIs() { |
| 847 | Frame appFrames[] = Frame.getFrames(); |
| 848 | for (int i = 0; i < appFrames.length; i++) { |
| 849 | updateWindowUI(appFrames[i]); |
| 850 | } |
| 851 | } |
| 852 | |
| 853 | /** |
| 854 | * Indicates if an updateUI call is pending. |
| 855 | */ |
| 856 | private static boolean updatePending; |
| 857 | |
| 858 | /** |
| 859 | * Sets whether or not an updateUI call is pending. |
| 860 | */ |
| 861 | private static synchronized void setUpdatePending(boolean update) { |
| 862 | updatePending = update; |
| 863 | } |
| 864 | |
| 865 | /** |
| 866 | * Returns true if a UI update is pending. |
| 867 | */ |
| 868 | private static synchronized boolean isUpdatePending() { |
| 869 | return updatePending; |
| 870 | } |
| 871 | |
| 872 | protected void updateUI() { |
| 873 | if (!isUpdatePending()) { |
| 874 | setUpdatePending(true); |
| 875 | Runnable uiUpdater = new Runnable() { |
| 876 | public void run() { |
| 877 | updateAllUIs(); |
| 878 | setUpdatePending(false); |
| 879 | } |
| 880 | }; |
| 881 | SwingUtilities.invokeLater(uiUpdater); |
| 882 | } |
| 883 | } |
| 884 | } |
| 885 | |
| 886 | private void writeObject(java.io.ObjectOutputStream out) |
| 887 | throws IOException { |
| 888 | throw new NotSerializableException(this.getClass().getName()); |
| 889 | } |
| 890 | |
| 891 | private class Handler implements PropertyChangeListener { |
| 892 | public void propertyChange(PropertyChangeEvent evt) { |
| 893 | String propertyName = evt.getPropertyName(); |
| 894 | Object newValue = evt.getNewValue(); |
| 895 | Object oldValue = evt.getOldValue(); |
| 896 | |
| 897 | if ("focusOwner" == propertyName) { |
| 898 | if (oldValue instanceof JComponent) { |
| 899 | repaintIfBackgroundsDiffer((JComponent)oldValue); |
| 900 | |
| 901 | } |
| 902 | |
| 903 | if (newValue instanceof JComponent) { |
| 904 | repaintIfBackgroundsDiffer((JComponent)newValue); |
| 905 | } |
| 906 | } |
| 907 | else if ("managingFocus" == propertyName) { |
| 908 | // De-register listener on old keyboard focus manager and |
| 909 | // register it on the new one. |
| 910 | KeyboardFocusManager manager = |
| 911 | (KeyboardFocusManager)evt.getSource(); |
| 912 | if (((Boolean)newValue).equals(Boolean.FALSE)) { |
| 913 | manager.removePropertyChangeListener(_handler); |
| 914 | } |
| 915 | else { |
| 916 | manager.addPropertyChangeListener(_handler); |
| 917 | } |
| 918 | } |
| 919 | } |
| 920 | |
| 921 | /** |
| 922 | * This is a support method that will check if the background colors of |
| 923 | * the specified component differ between focused and unfocused states. |
| 924 | * If the color differ the component will then repaint itself. |
| 925 | * |
| 926 | * @comp the component to check |
| 927 | */ |
| 928 | private void repaintIfBackgroundsDiffer(JComponent comp) { |
| 929 | ComponentUI ui = (ComponentUI)comp.getClientProperty( |
| 930 | SwingUtilities2.COMPONENT_UI_PROPERTY_KEY); |
| 931 | if (ui instanceof SynthUI) { |
| 932 | SynthUI synthUI = (SynthUI)ui; |
| 933 | SynthContext context = synthUI.getContext(comp); |
| 934 | SynthStyle style = context.getStyle(); |
| 935 | int state = context.getComponentState(); |
| 936 | |
| 937 | // Get the current background color. |
| 938 | Color currBG = style.getColor(context, ColorType.BACKGROUND); |
| 939 | |
| 940 | // Get the last background color. |
| 941 | state ^= SynthConstants.FOCUSED; |
| 942 | context.setComponentState(state); |
| 943 | Color lastBG = style.getColor(context, ColorType.BACKGROUND); |
| 944 | |
| 945 | // Reset the component state back to original. |
| 946 | state ^= SynthConstants.FOCUSED; |
| 947 | context.setComponentState(state); |
| 948 | |
| 949 | // Repaint the component if the backgrounds differed. |
| 950 | if (currBG != null && !currBG.equals(lastBG)) { |
| 951 | comp.repaint(); |
| 952 | } |
| 953 | context.dispose(); |
| 954 | } |
| 955 | } |
| 956 | } |
| 957 | } |