001 /* 002 * $Id: UIManagerExt.java 2757 2008-02-26 04:31:50Z kschaefe $ 003 * 004 * Copyright 2007 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021 package org.jdesktop.swingx.plaf; 022 023 import java.awt.Color; 024 import java.awt.Dimension; 025 import java.awt.Font; 026 import java.awt.Insets; 027 import java.awt.Shape; 028 import java.util.Enumeration; 029 import java.util.HashMap; 030 import java.util.Locale; 031 import java.util.Map; 032 import java.util.MissingResourceException; 033 import java.util.ResourceBundle; 034 import java.util.Vector; 035 036 import javax.swing.Icon; 037 import javax.swing.UIDefaults; 038 import javax.swing.UIManager; 039 import javax.swing.border.Border; 040 import javax.swing.plaf.BorderUIResource; 041 import javax.swing.plaf.ColorUIResource; 042 import javax.swing.plaf.DimensionUIResource; 043 import javax.swing.plaf.FontUIResource; 044 import javax.swing.plaf.IconUIResource; 045 import javax.swing.plaf.InsetsUIResource; 046 import javax.swing.plaf.UIResource; 047 048 import org.jdesktop.swingx.painter.Painter; 049 import org.jdesktop.swingx.util.Contract; 050 051 /** 052 * A utility class for obtaining configuration properties from the 053 * {@code UIDefaults}. This class handles SwingX-specific L&F needs, such as 054 * the installation of painters and shapes. There are several categories of 055 * utility methods: 056 * <ul> 057 * <li>Support for the safe creation of {@code UIResource}s.</li> 058 * <li>Support for new {@code UIResource} types, such as 059 * {@code PainterUIResource}.</li> 060 * <li>Support for the dynamic localization of {@code UIDefaults}.</li> 061 * <li>Support for returning non-{@code String} localizations from 062 * {@code ResourceBundle}s.</li> 063 * </ul> 064 * <h3>Safe Methods</h3> 065 * <p> 066 * The {@code getSafeXXX} methods are designed for use with 067 * {@code LookAndFeelAddon}s. Any addon that attempts to obtain a property 068 * defined in the defaults (available from {@code UIManager.get}) to set a 069 * property that will be added to the defaults for the addon should use the 070 * "safe" methods. The methods ensure that a valid value is always returned and 071 * that value is a {@code UIResource}. 072 * </p> 073 * <h3>Support for New Types</h3> 074 * <p> 075 * {@code UIManagerExt} supports the retrieval of new {@code UIResource} types. 076 * There is a {@code getXXX} method for every {@code UIResource} subtype in the 077 * {@code org.jdesktop.swingx.plaf} package. 078 * </p> 079 * <h3>Support for Dynamic Localization</h3> 080 * <p> 081 * {@code UIManagerExt} enables dynamic localization by supporting 082 * {@code ResourceBundle}s. The 083 * {@linkplain UIDefaults#addResourceBundle(String)} allows resource bundles to 084 * be added to the {@code UIDefaults}. While there is support for this feature 085 * in core, there is a bug with the class loader that prevents user added 086 * bundles from working correctly when used via Web Start. Therefore, 087 * {@code UIManagerExt} defines methods to add and remove resource bundles. 088 * These are the only methods that SwingX classes should use when adding 089 * resource bundles to the defaults. Since {@code UIManagerExt} is maintaining 090 * the bundles, any localized {@code String}s <b>must</b> be retrieved from 091 * the {@code getString} methods in this class. 092 * </p> 093 * <h3>Support for Non-{@code String} Localization Values</h3> 094 * <p> 095 * All methods work by first determining if the value is present 096 * {@code UIDefaults}. If the value is not present, then the installed 097 * {@code ResourceBundle}s are queried. {@code UIManagerExt} will attempt to 098 * convert any returned value to the appropriate type. For instance, 099 * {@code getInt} uses {@code Integer.decode} to convert {@code String}s 100 * returned from the bundle into {@code int}s. 101 * </p> 102 * 103 * @author Karl George Schaefer 104 * 105 * @see UIManager 106 * @see UIDefaults 107 */ 108 public class UIManagerExt { 109 /** 110 * Used to replicate the resource bundle behavior from the 111 * {@code UIDefaults}. 112 */ 113 private static class UIDefaultsExt { 114 //use vector; we want synchronization 115 private Vector<String> resourceBundles; 116 117 /** 118 * Maps from a Locale to a cached Map of the ResourceBundle. This is done 119 * so as to avoid an exception being thrown when a value is asked for. 120 * Access to this should be done while holding a lock on the 121 * UIDefaults, eg synchronized(this). 122 */ 123 private Map<Locale, Map<String, String>> resourceCache; 124 125 UIDefaultsExt() { 126 resourceCache = new HashMap<Locale, Map<String,String>>(); 127 } 128 129 //should this just return String? 130 private Object getFromResourceBundle(Object key, Locale l) { 131 132 if( resourceBundles == null || 133 resourceBundles.isEmpty() || 134 !(key instanceof String) ) { 135 return null; 136 } 137 138 // A null locale means use the default locale. 139 if( l == null ) { 140 l = Locale.getDefault(); 141 } 142 143 synchronized(this) { 144 return getResourceCache(l).get((String)key); 145 } 146 } 147 148 /** 149 * Returns a Map of the known resources for the given locale. 150 */ 151 private Map<String, String> getResourceCache(Locale l) { 152 Map<String, String> values = (Map<String, String>) resourceCache.get(l); 153 154 if (values == null) { 155 values = new HashMap<String, String>(); 156 for (int i=resourceBundles.size()-1; i >= 0; i--) { 157 String bundleName = (String)resourceBundles.get(i); 158 159 try { 160 ResourceBundle b = ResourceBundle. 161 getBundle(bundleName, l, UIManagerExt.class.getClassLoader()); 162 Enumeration<String> keys = b.getKeys(); 163 164 while (keys.hasMoreElements()) { 165 String key = (String)keys.nextElement(); 166 167 if (values.get(key) == null) { 168 Object value = b.getObject(key); 169 170 values.put(key, (String) value); 171 } 172 } 173 } catch( MissingResourceException mre ) { 174 // Keep looking 175 } 176 } 177 resourceCache.put(l, values); 178 } 179 return values; 180 } 181 182 public synchronized void addResourceBundle(String bundleName) { 183 if( bundleName == null ) { 184 return; 185 } 186 if( resourceBundles == null ) { 187 resourceBundles = new Vector<String>(5); 188 } 189 if (!resourceBundles.contains(bundleName)) { 190 resourceBundles.add( bundleName ); 191 resourceCache.clear(); 192 } 193 } 194 195 public synchronized void removeResourceBundle( String bundleName ) { 196 if( resourceBundles != null ) { 197 resourceBundles.remove( bundleName ); 198 } 199 resourceCache.clear(); 200 } 201 } 202 203 private static UIDefaultsExt uiDefaultsExt = new UIDefaultsExt(); 204 205 private UIManagerExt() { 206 //does nothing 207 } 208 209 /** 210 * Adds a resource bundle to the list of resource bundles that are searched 211 * for localized values. Resource bundles are searched in the reverse order 212 * they were added. In other words, the most recently added bundle is 213 * searched first. 214 * 215 * @param bundleName 216 * the base name of the resource bundle to be added 217 * @see java.util.ResourceBundle 218 * @see #removeResourceBundle 219 */ 220 public static void addResourceBundle(String bundleName) { 221 uiDefaultsExt.addResourceBundle(bundleName); 222 } 223 224 /** 225 * Removes a resource bundle from the list of resource bundles that are 226 * searched for localized defaults. 227 * 228 * @param bundleName 229 * the base name of the resource bundle to be removed 230 * @see java.util.ResourceBundle 231 * @see #addResourceBundle 232 */ 233 public static void removeResourceBundle(String bundleName) { 234 uiDefaultsExt.removeResourceBundle(bundleName); 235 } 236 237 /** 238 * Returns a string from the defaults. If the value for {@code key} is not a 239 * {@code String}, {@code null} is returned. 240 * 241 * @param key 242 * an {@code Object} specifying the string 243 * @return the {@code String} object 244 * @throws NullPointerException 245 * if {@code key} is {@code null} 246 */ 247 public static String getString(Object key) { 248 return getString(key, null); 249 } 250 251 /** 252 * Returns a string from the defaults. If the value for {@code key} is not a 253 * {@code String}, {@code null} is returned. 254 * 255 * @param key 256 * an {@code Object} specifying the string 257 * @param l 258 * the {@code Locale} for which the painter is desired; refer 259 * to {@code UIDefaults} for details on how a {@code null} 260 * {@code Locale} is handled 261 * @return the {@code String} object 262 * @throws NullPointerException 263 * if {@code key} is {@code null} 264 */ 265 public static String getString(Object key, Locale l) { 266 Object value = UIManager.get(key, l); 267 268 if (value instanceof String) { 269 return (String) value; 270 } 271 272 //only return resource bundle if not in UIDefaults 273 if (value == null) { 274 value = uiDefaultsExt.getFromResourceBundle(key, l); 275 276 if (value instanceof String) { 277 return (String) value; 278 } 279 } 280 281 return null; 282 } 283 284 /** 285 * Returns an integer from the defaults. If the value for {@code key} is not 286 * an {@code int}, {@code 0} is returned. 287 * 288 * @param key 289 * an {@code Object} specifying the integer 290 * @return the {@code int} 291 * @throws NullPointerException 292 * if {@code key} is {@code null} 293 */ 294 public static int getInt(Object key) { 295 return getInt(key, null); 296 } 297 298 /** 299 * Returns an integer from the defaults. If the value for {@code key} is not 300 * an {@code int}, {@code 0} is returned. 301 * 302 * @param key 303 * an {@code Object} specifying the integer 304 * @param l 305 * the {@code Locale} for which the integer is desired; refer 306 * to {@code UIDefaults} for details on how a {@code null} 307 * {@code Locale} is handled 308 * @return the {@code int} 309 * @throws NullPointerException 310 * if {@code key} is {@code null} 311 */ 312 public static int getInt(Object key, Locale l) { 313 Object value = UIManager.get(key, l); 314 315 if (value instanceof Integer) { 316 return (Integer) value; 317 } 318 319 if (value == null) { 320 value = uiDefaultsExt.getFromResourceBundle(key, l); 321 322 if (value instanceof Integer) { 323 return (Integer) value; 324 } 325 326 if (value instanceof String) { 327 try { 328 return Integer.decode((String) value); 329 } catch (NumberFormatException e) { 330 // ignore - the entry was not parseable, can't do anything 331 // JW: should we log it? 332 } 333 } 334 } 335 336 return 0; 337 } 338 339 /** 340 * Returns an Boolean from the defaults. If the value for {@code key} is not 341 * a {@code boolean}, {@code false} is returned. 342 * 343 * @param key 344 * an {@code Object} specifying the Boolean 345 * @return the {@code boolean} 346 * @throws NullPointerException 347 * if {@code key} is {@code null} 348 */ 349 public static boolean getBoolean(Object key) { 350 return getBoolean(key, null); 351 } 352 353 /** 354 * Returns an Boolean from the defaults. If the value for {@code key} is not 355 * a {@code boolean}, {@code false} is returned. 356 * 357 * @param key 358 * an {@code Object} specifying the Boolean 359 * @param l 360 * the {@code Locale} for which the Boolean is desired; refer 361 * to {@code UIDefaults} for details on how a {@code null} 362 * {@code Locale} is handled 363 * @return the {@code boolean} 364 * @throws NullPointerException 365 * if {@code key} is {@code null} 366 */ 367 public static boolean getBoolean(Object key, Locale l) { 368 Object value = UIManager.get(key, l); 369 370 if (value instanceof Boolean) { 371 return (Boolean) value; 372 } 373 374 //only return resource bundle if not in UIDefaults 375 if (value == null) { 376 value = uiDefaultsExt.getFromResourceBundle(key, l); 377 378 if (value instanceof Boolean) { 379 return (Boolean) value; 380 } 381 382 if (value instanceof String) { 383 return Boolean.valueOf((String) value); 384 } 385 } 386 387 return false; 388 } 389 390 /** 391 * Returns a color from the defaults. If the value for {@code key} is not 392 * a {@code Color}, {@code null} is returned. 393 * 394 * @param key 395 * an {@code Object} specifying the color 396 * @return the {@code Color} object 397 * @throws NullPointerException 398 * if {@code key} is {@code null} 399 */ 400 public static Color getColor(Object key) { 401 return getColor(key, null); 402 } 403 404 /** 405 * Returns a color from the defaults. If the value for {@code key} is not 406 * a {@code Color}, {@code null} is returned. 407 * 408 * @param key 409 * an {@code Object} specifying the color 410 * @param l 411 * the {@code Locale} for which the color is desired; refer 412 * to {@code UIDefaults} for details on how a {@code null} 413 * {@code Locale} is handled 414 * @return the {@code Color} object 415 * @throws NullPointerException 416 * if {@code key} is {@code null} 417 */ 418 public static Color getColor(Object key, Locale l) { 419 Object value = UIManager.get(key, l); 420 421 if (value instanceof Color) { 422 return (Color) value; 423 } 424 425 //only return resource bundle if not in UIDefaults 426 if (value == null) { 427 value = uiDefaultsExt.getFromResourceBundle(key, l); 428 429 if (value instanceof Color) { 430 return (Color) value; 431 } 432 433 if (value instanceof String) { 434 try { 435 return Color.decode((String) value); 436 } catch (NumberFormatException e) { 437 // incorrect format; does nothing 438 } 439 } 440 } 441 442 return null; 443 } 444 445 //TODO: Font.decode always returns a valid font. This is not acceptable for UIManager 446 // /** 447 // * Returns a font from the defaults. If the value for {@code key} is not 448 // * a {@code Font}, {@code null} is returned. 449 // * 450 // * @param key 451 // * an {@code Object} specifying the font 452 // * @return the {@code Font} object 453 // * @throws NullPointerException 454 // * if {@code key} is {@code null} 455 // */ 456 // public static Font getFont(Object key) { 457 // return getFont(key, null); 458 // } 459 // 460 // /** 461 // * Returns a font from the defaults. If the value for {@code key} is not 462 // * a {@code Font}, {@code null} is returned. 463 // * 464 // * @param key 465 // * an {@code Object} specifying the font 466 // * @param l 467 // * the {@code Locale} for which the font is desired; refer 468 // * to {@code UIDefaults} for details on how a {@code null} 469 // * {@code Locale} is handled 470 // * @return the {@code Font} object 471 // * @throws NullPointerException 472 // * if {@code key} is {@code null} 473 // */ 474 // public static Font getFont(Object key, Locale l) { 475 // Object value = UIManager.get(key, l); 476 // 477 // if (value instanceof Font) { 478 // return (Font) value; 479 // } 480 // 481 // //only return resource bundle if not in UIDefaults 482 // if (value == null) { 483 // value = uiDefaultsExt.getFromResourceBundle(key, l); 484 // 485 // if (value instanceof Font) { 486 // return (Font) value; 487 // } 488 // 489 // if (value instanceof String) { 490 // return Font.decode((String) value); 491 // } 492 // } 493 // 494 // return null; 495 // } 496 497 /** 498 * Returns a shape from the defaults. If the value for {@code key} is not a 499 * {@code Shape}, {@code null} is returned. 500 * 501 * @param key an {@code Object} specifying the shape 502 * @return the {@code Shape} object 503 * @throws NullPointerException if {@code key} is {@code null} 504 */ 505 public static Shape getShape(Object key) { 506 Object value = UIManager.getDefaults().get(key); 507 return (value instanceof Shape) ? (Shape) value : null; 508 } 509 510 /** 511 * Returns a shape from the defaults that is appropriate for the given 512 * locale. If the value for {@code key} is not a {@code Shape}, 513 * {@code null} is returned. 514 * 515 * @param key 516 * an {@code Object} specifying the shape 517 * @param l 518 * the {@code Locale} for which the shape is desired; refer 519 * to {@code UIDefaults} for details on how a {@code null} 520 * {@code Locale} is handled 521 * @return the {@code Shape} object 522 * @throws NullPointerException 523 * if {@code key} is {@code null} 524 */ 525 public static Shape getShape(Object key, Locale l) { 526 Object value = UIManager.getDefaults().get(key, l); 527 return (value instanceof Shape) ? (Shape) value : null; 528 } 529 530 /** 531 * Returns a painter from the defaults. If the value for {@code key} is not 532 * a {@code Painter}, {@code null} is returned. 533 * 534 * @param key 535 * an {@code Object} specifying the painter 536 * @return the {@code Painter} object 537 * @throws NullPointerException 538 * if {@code key} is {@code null} 539 */ 540 public static Painter<?> getPainter(Object key) { 541 Object value = UIManager.getDefaults().get(key); 542 return (value instanceof Painter) ? (Painter<?>) value : null; 543 } 544 545 /** 546 * Returns a painter from the defaults that is appropriate for the given 547 * locale. If the value for {@code key} is not a {@code Painter}, 548 * {@code null} is returned. 549 * 550 * @param key 551 * an {@code Object} specifying the painter 552 * @param l 553 * the {@code Locale} for which the painter is desired; refer 554 * to {@code UIDefaults} for details on how a {@code null} 555 * {@code Locale} is handled 556 * @return the {@code Painter} object 557 * @throws NullPointerException 558 * if {@code key} is {@code null} 559 */ 560 public static Painter<?> getPainter(Object key, Locale l) { 561 Object value = UIManager.getDefaults().get(key, l); 562 return (value instanceof Painter) ? (Painter<?>) value : null; 563 } 564 565 /** 566 * Returns a border from the defaults. If the value for {@code key} is not a 567 * {@code Border}, {@code defaultBorder} is returned. 568 * 569 * @param key 570 * an {@code Object} specifying the border 571 * @param defaultBorder 572 * the border to return if the border specified by 573 * {@code key} does not exist 574 * @return the {@code Border} object 575 * @throws NullPointerException 576 * if {@code key} or {@code defaultBorder} is {@code null} 577 */ 578 public static Border getSafeBorder(Object key, Border defaultBorder) { 579 Contract.asNotNull(defaultBorder, "defaultBorder cannot be null"); 580 581 Border safeBorder = UIManager.getBorder(key); 582 583 if (safeBorder == null) { 584 safeBorder = defaultBorder; 585 } 586 587 if (!(safeBorder instanceof UIResource)) { 588 safeBorder = new BorderUIResource(safeBorder); 589 } 590 591 return safeBorder; 592 } 593 594 /** 595 * Returns a color from the defaults. If the value for {@code key} is not a 596 * {@code Color}, {@code defaultColor} is returned. 597 * 598 * @param key 599 * an {@code Object} specifying the color 600 * @param defaultColor 601 * the color to return if the color specified by {@code key} 602 * does not exist 603 * @return the {@code Color} object 604 * @throws NullPointerException 605 * if {@code key} or {@code defaultColor} is {@code null} 606 */ 607 public static Color getSafeColor(Object key, Color defaultColor) { 608 Contract.asNotNull(defaultColor, "defaultColor cannot be null"); 609 610 Color safeColor = UIManager.getColor(key); 611 612 if (safeColor == null) { 613 safeColor = defaultColor; 614 } 615 616 if (!(safeColor instanceof UIResource)) { 617 safeColor = new ColorUIResource(safeColor); 618 } 619 620 return safeColor; 621 } 622 623 /** 624 * Returns a dimension from the defaults. If the value for {@code key} is 625 * not a {@code Dimension}, {@code defaultDimension} is returned. 626 * 627 * @param key 628 * an {@code Object} specifying the dimension 629 * @param defaultDimension 630 * the dimension to return if the dimension specified by 631 * {@code key} does not exist 632 * @return the {@code Dimension} object 633 * @throws NullPointerException 634 * if {@code key} or {@code defaultColor} is {@code null} 635 */ 636 public static Dimension getSafeDimension(Object key, Dimension defaultDimension) { 637 Contract.asNotNull(defaultDimension, "defaultDimension cannot be null"); 638 639 Dimension safeDimension = UIManager.getDimension(key); 640 641 if (safeDimension == null) { 642 safeDimension = defaultDimension; 643 } 644 645 if (!(safeDimension instanceof UIResource)) { 646 safeDimension = new DimensionUIResource(safeDimension.width, safeDimension.height); 647 } 648 649 return safeDimension; 650 } 651 652 /** 653 * Returns a font from the defaults. If the value for {@code key} is not a 654 * {@code Font}, {@code defaultFont} is returned. 655 * 656 * @param key 657 * an {@code Object} specifying the font 658 * @param defaultFont 659 * the font to return if the font specified by {@code key} 660 * does not exist 661 * @return the {@code Font} object 662 * @throws NullPointerException 663 * if {@code key} or {@code defaultFont} is {@code null} 664 */ 665 public static Font getSafeFont(Object key, Font defaultFont) { 666 Contract.asNotNull(defaultFont, "defaultFont cannot be null"); 667 668 Font safeFont = UIManager.getFont(key); 669 670 if (safeFont == null) { 671 safeFont = defaultFont; 672 } 673 674 if (!(safeFont instanceof UIResource)) { 675 safeFont = new FontUIResource(safeFont); 676 } 677 678 return safeFont; 679 } 680 681 /** 682 * Returns an icon from the defaults. If the value for {@code key} is not a 683 * {@code Icon}, {@code defaultIcon} is returned. 684 * 685 * @param key 686 * an {@code Object} specifying the icon 687 * @param defaultIcon 688 * the icon to return if the icon specified by {@code key} 689 * does not exist 690 * @return the {@code Icon} object 691 * @throws NullPointerException 692 * if {@code key} or {@code defaultIcon} is {@code null} 693 */ 694 public static Icon getSafeIcon(Object key, Icon defaultIcon) { 695 Contract.asNotNull(defaultIcon, "defaultIcon cannot be null"); 696 697 Icon safeIcon = UIManager.getIcon(key); 698 699 if (safeIcon == null) { 700 safeIcon = defaultIcon; 701 } 702 703 if (!(safeIcon instanceof UIResource)) { 704 safeIcon = new IconUIResource(safeIcon); 705 } 706 707 return safeIcon; 708 } 709 710 /** 711 * Returns an insets from the defaults. If the value for {@code key} is not 712 * a {@code Insets}, {@code defaultInsets} is returned. 713 * 714 * @param key 715 * an {@code Object} specifying the insets 716 * @param defaultInsets 717 * the insets to return if the insets specified by 718 * {@code key} does not exist 719 * @return the {@code Insets} object 720 * @throws NullPointerException 721 * if {@code key} or {@code defaultInsets} is {@code null} 722 */ 723 public static Insets getSafeInsets(Object key, Insets defaultInsets) { 724 Contract.asNotNull(defaultInsets, "defaultInsets cannot be null"); 725 726 Insets safeInsets = UIManager.getInsets(key); 727 728 if (safeInsets == null) { 729 safeInsets = defaultInsets; 730 } 731 732 if (!(safeInsets instanceof UIResource)) { 733 safeInsets = new InsetsUIResource(safeInsets.top, safeInsets.left, 734 safeInsets.bottom, safeInsets.right); 735 } 736 737 return safeInsets; 738 } 739 }