001/* 002 * $Id: UIManagerExt.java 4028 2011-06-03 19:32:19Z 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 */ 021package org.jdesktop.swingx.plaf; 022 023import java.awt.Color; 024import java.awt.Dimension; 025import java.awt.Font; 026import java.awt.Insets; 027import java.awt.Shape; 028import java.util.Enumeration; 029import java.util.HashMap; 030import java.util.Locale; 031import java.util.Map; 032import java.util.MissingResourceException; 033import java.util.ResourceBundle; 034import java.util.Vector; 035 036import javax.swing.Icon; 037import javax.swing.UIDefaults; 038import javax.swing.UIManager; 039import javax.swing.border.Border; 040import javax.swing.plaf.BorderUIResource; 041import javax.swing.plaf.ColorUIResource; 042import javax.swing.plaf.DimensionUIResource; 043import javax.swing.plaf.FontUIResource; 044import javax.swing.plaf.IconUIResource; 045import javax.swing.plaf.InsetsUIResource; 046import javax.swing.plaf.UIResource; 047 048import org.jdesktop.swingx.painter.Painter; 049import 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@SuppressWarnings("nls") 109public class UIManagerExt { 110 /** 111 * Used to replicate the resource bundle behavior from the 112 * {@code UIDefaults}. 113 */ 114 private static class UIDefaultsExt { 115 //use vector; we want synchronization 116 private Vector<String> resourceBundles; 117 118 /** 119 * Maps from a Locale to a cached Map of the ResourceBundle. This is done 120 * so as to avoid an exception being thrown when a value is asked for. 121 * Access to this should be done while holding a lock on the 122 * UIDefaults, eg synchronized(this). 123 */ 124 private Map<Locale, Map<String, String>> resourceCache; 125 126 UIDefaultsExt() { 127 resourceCache = new HashMap<Locale, Map<String,String>>(); 128 } 129 130 //should this just return String? 131 private Object getFromResourceBundle(Object key, Locale l) { 132 133 if( resourceBundles == null || 134 resourceBundles.isEmpty() || 135 !(key instanceof String) ) { 136 return null; 137 } 138 139 // A null locale means use the default locale. 140 if( l == null ) { 141 l = Locale.getDefault(); 142 } 143 144 synchronized(this) { 145 return getResourceCache(l).get(key); 146 } 147 } 148 149 /** 150 * Returns a Map of the known resources for the given locale. 151 */ 152 private Map<String, String> getResourceCache(Locale l) { 153 Map<String, String> values = resourceCache.get(l); 154 155 if (values == null) { 156 values = new HashMap<String, String>(); 157 for (int i=resourceBundles.size()-1; i >= 0; i--) { 158 String bundleName = resourceBundles.get(i); 159 160 try { 161 ResourceBundle b = ResourceBundle. 162 getBundle(bundleName, l, UIManagerExt.class.getClassLoader()); 163 Enumeration<String> keys = b.getKeys(); 164 165 while (keys.hasMoreElements()) { 166 String key = keys.nextElement(); 167 168 if (values.get(key) == null) { 169 Object value = b.getObject(key); 170 171 values.put(key, (String) value); 172 } 173 } 174 } catch( MissingResourceException mre ) { 175 // Keep looking 176 } 177 } 178 resourceCache.put(l, values); 179 } 180 return values; 181 } 182 183 public synchronized void addResourceBundle(String bundleName) { 184 if( bundleName == null ) { 185 return; 186 } 187 if( resourceBundles == null ) { 188 resourceBundles = new Vector<String>(5); 189 } 190 if (!resourceBundles.contains(bundleName)) { 191 resourceBundles.add( bundleName ); 192 resourceCache.clear(); 193 } 194 } 195 196 public synchronized void removeResourceBundle( String bundleName ) { 197 if( resourceBundles != null ) { 198 resourceBundles.remove( bundleName ); 199 } 200 resourceCache.clear(); 201 } 202 } 203 204 private static UIDefaultsExt uiDefaultsExt = new UIDefaultsExt(); 205 206 private UIManagerExt() { 207 //does nothing 208 } 209 210 /** 211 * Adds a resource bundle to the list of resource bundles that are searched 212 * for localized values. Resource bundles are searched in the reverse order 213 * they were added. In other words, the most recently added bundle is 214 * searched first. 215 * 216 * @param bundleName 217 * the base name of the resource bundle to be added 218 * @see java.util.ResourceBundle 219 * @see #removeResourceBundle 220 */ 221 public static void addResourceBundle(String bundleName) { 222 uiDefaultsExt.addResourceBundle(bundleName); 223 } 224 225 /** 226 * Removes a resource bundle from the list of resource bundles that are 227 * searched for localized defaults. 228 * 229 * @param bundleName 230 * the base name of the resource bundle to be removed 231 * @see java.util.ResourceBundle 232 * @see #addResourceBundle 233 */ 234 public static void removeResourceBundle(String bundleName) { 235 uiDefaultsExt.removeResourceBundle(bundleName); 236 } 237 238 /** 239 * Returns a string from the defaults. If the value for {@code key} is not a 240 * {@code String}, {@code null} is returned. 241 * 242 * @param key 243 * an {@code Object} specifying the string 244 * @return the {@code String} object 245 * @throws NullPointerException 246 * if {@code key} is {@code null} 247 */ 248 public static String getString(Object key) { 249 return getString(key, null); 250 } 251 252 /** 253 * Returns a string from the defaults. If the value for {@code key} is not a 254 * {@code String}, {@code null} is returned. 255 * 256 * @param key 257 * an {@code Object} specifying the string 258 * @param l 259 * the {@code Locale} for which the string is desired; refer 260 * to {@code UIDefaults} for details on how a {@code null} 261 * {@code Locale} is handled 262 * @return the {@code String} object 263 * @throws NullPointerException 264 * if {@code key} is {@code null} 265 */ 266 public static String getString(Object key, Locale l) { 267 Object value = UIManager.get(key, l); 268 269 if (value instanceof String) { 270 return (String) value; 271 } 272 273 //only return resource bundle if not in UIDefaults 274 if (value == null) { 275 value = uiDefaultsExt.getFromResourceBundle(key, l); 276 277 if (value instanceof String) { 278 return (String) value; 279 } 280 } 281 282 return null; 283 } 284 285 /** 286 * Returns an integer from the defaults. If the value for {@code key} is not 287 * an {@code int}, {@code 0} is returned. 288 * 289 * @param key 290 * an {@code Object} specifying the integer 291 * @return the {@code int} 292 * @throws NullPointerException 293 * if {@code key} is {@code null} 294 */ 295 public static int getInt(Object key) { 296 return getInt(key, null); 297 } 298 299 /** 300 * Returns an integer from the defaults. If the value for {@code key} is not 301 * an {@code int}, {@code 0} is returned. 302 * 303 * @param key 304 * an {@code Object} specifying the integer 305 * @param l 306 * the {@code Locale} for which the integer is desired; refer 307 * to {@code UIDefaults} for details on how a {@code null} 308 * {@code Locale} is handled 309 * @return the {@code int} 310 * @throws NullPointerException 311 * if {@code key} is {@code null} 312 */ 313 public static int getInt(Object key, Locale l) { 314 Object value = UIManager.get(key, l); 315 316 if (value instanceof Integer) { 317 return (Integer) value; 318 } 319 320 if (value == null) { 321 value = uiDefaultsExt.getFromResourceBundle(key, l); 322 323 if (value instanceof Integer) { 324 return (Integer) value; 325 } 326 327 if (value instanceof String) { 328 try { 329 return Integer.decode((String) value); 330 } catch (NumberFormatException e) { 331 // ignore - the entry was not parseable, can't do anything 332 // JW: should we log it? 333 } 334 } 335 } 336 337 return 0; 338 } 339 340 /** 341 * Returns an Boolean from the defaults. If the value for {@code key} is not 342 * a {@code boolean}, {@code false} is returned. 343 * 344 * @param key 345 * an {@code Object} specifying the Boolean 346 * @return the {@code boolean} 347 * @throws NullPointerException 348 * if {@code key} is {@code null} 349 */ 350 public static boolean getBoolean(Object key) { 351 return getBoolean(key, null); 352 } 353 354 /** 355 * Returns an Boolean from the defaults. If the value for {@code key} is not 356 * a {@code boolean}, {@code false} is returned. 357 * 358 * @param key 359 * an {@code Object} specifying the Boolean 360 * @param l 361 * the {@code Locale} for which the Boolean is desired; refer 362 * to {@code UIDefaults} for details on how a {@code null} 363 * {@code Locale} is handled 364 * @return the {@code boolean} 365 * @throws NullPointerException 366 * if {@code key} is {@code null} 367 */ 368 public static boolean getBoolean(Object key, Locale l) { 369 Object value = UIManager.get(key, l); 370 371 if (value instanceof Boolean) { 372 return (Boolean) value; 373 } 374 375 //only return resource bundle if not in UIDefaults 376 if (value == null) { 377 value = uiDefaultsExt.getFromResourceBundle(key, l); 378 379 if (value instanceof Boolean) { 380 return (Boolean) value; 381 } 382 383 if (value instanceof String) { 384 return Boolean.valueOf((String) value); 385 } 386 } 387 388 return false; 389 } 390 391 /** 392 * Returns a color from the defaults. If the value for {@code key} is not 393 * a {@code Color}, {@code null} is returned. 394 * 395 * @param key 396 * an {@code Object} specifying the color 397 * @return the {@code Color} object 398 * @throws NullPointerException 399 * if {@code key} is {@code null} 400 */ 401 public static Color getColor(Object key) { 402 return getColor(key, null); 403 } 404 405 /** 406 * Returns a color from the defaults. If the value for {@code key} is not 407 * a {@code Color}, {@code null} is returned. 408 * 409 * @param key 410 * an {@code Object} specifying the color 411 * @param l 412 * the {@code Locale} for which the color is desired; refer 413 * to {@code UIDefaults} for details on how a {@code null} 414 * {@code Locale} is handled 415 * @return the {@code Color} object 416 * @throws NullPointerException 417 * if {@code key} is {@code null} 418 */ 419 public static Color getColor(Object key, Locale l) { 420 Object value = UIManager.get(key, l); 421 422 if (value instanceof Color) { 423 return (Color) value; 424 } 425 426 //only return resource bundle if not in UIDefaults 427 if (value == null) { 428 value = uiDefaultsExt.getFromResourceBundle(key, l); 429 430 if (value instanceof Color) { 431 return (Color) value; 432 } 433 434 if (value instanceof String) { 435 try { 436 return Color.decode((String) value); 437 } catch (NumberFormatException e) { 438 // incorrect format; does nothing 439 } 440 } 441 } 442 443 return null; 444 } 445 446 //TODO: Font.decode always returns a valid font. This is not acceptable for UIManager 447// /** 448// * Returns a font from the defaults. If the value for {@code key} is not 449// * a {@code Font}, {@code null} is returned. 450// * 451// * @param key 452// * an {@code Object} specifying the font 453// * @return the {@code Font} object 454// * @throws NullPointerException 455// * if {@code key} is {@code null} 456// */ 457// public static Font getFont(Object key) { 458// return getFont(key, null); 459// } 460// 461// /** 462// * Returns a font from the defaults. If the value for {@code key} is not 463// * a {@code Font}, {@code null} is returned. 464// * 465// * @param key 466// * an {@code Object} specifying the font 467// * @param l 468// * the {@code Locale} for which the font is desired; refer 469// * to {@code UIDefaults} for details on how a {@code null} 470// * {@code Locale} is handled 471// * @return the {@code Font} object 472// * @throws NullPointerException 473// * if {@code key} is {@code null} 474// */ 475// public static Font getFont(Object key, Locale l) { 476// Object value = UIManager.get(key, l); 477// 478// if (value instanceof Font) { 479// return (Font) value; 480// } 481// 482// //only return resource bundle if not in UIDefaults 483// if (value == null) { 484// value = uiDefaultsExt.getFromResourceBundle(key, l); 485// 486// if (value instanceof Font) { 487// return (Font) value; 488// } 489// 490// if (value instanceof String) { 491// return Font.decode((String) value); 492// } 493// } 494// 495// return null; 496// } 497 498 /** 499 * Returns a shape from the defaults. If the value for {@code key} is not a 500 * {@code Shape}, {@code null} is returned. 501 * 502 * @param key an {@code Object} specifying the shape 503 * @return the {@code Shape} object 504 * @throws NullPointerException if {@code key} is {@code null} 505 */ 506 public static Shape getShape(Object key) { 507 Object value = UIManager.getDefaults().get(key); 508 return (value instanceof Shape) ? (Shape) value : null; 509 } 510 511 /** 512 * Returns a shape from the defaults that is appropriate for the given 513 * locale. If the value for {@code key} is not a {@code Shape}, 514 * {@code null} is returned. 515 * 516 * @param key 517 * an {@code Object} specifying the shape 518 * @param l 519 * the {@code Locale} for which the shape is desired; refer 520 * to {@code UIDefaults} for details on how a {@code null} 521 * {@code Locale} is handled 522 * @return the {@code Shape} object 523 * @throws NullPointerException 524 * if {@code key} is {@code null} 525 */ 526 public static Shape getShape(Object key, Locale l) { 527 Object value = UIManager.getDefaults().get(key, l); 528 return (value instanceof Shape) ? (Shape) value : null; 529 } 530 531 /** 532 * Returns a painter from the defaults. If the value for {@code key} is not 533 * a {@code Painter}, {@code null} is returned. 534 * 535 * @param key 536 * an {@code Object} specifying the painter 537 * @return the {@code Painter} object 538 * @throws NullPointerException 539 * if {@code key} is {@code null} 540 */ 541 public static Painter<?> getPainter(Object key) { 542 Object value = UIManager.getDefaults().get(key); 543 return (value instanceof Painter<?>) ? (Painter<?>) value : null; 544 } 545 546 /** 547 * Returns a painter from the defaults that is appropriate for the given 548 * locale. If the value for {@code key} is not a {@code Painter}, 549 * {@code null} is returned. 550 * 551 * @param key 552 * an {@code Object} specifying the painter 553 * @param l 554 * the {@code Locale} for which the painter is desired; refer 555 * to {@code UIDefaults} for details on how a {@code null} 556 * {@code Locale} is handled 557 * @return the {@code Painter} object 558 * @throws NullPointerException 559 * if {@code key} is {@code null} 560 */ 561 public static Painter<?> getPainter(Object key, Locale l) { 562 Object value = UIManager.getDefaults().get(key, l); 563 return (value instanceof Painter<?>) ? (Painter<?>) value : null; 564 } 565 566 /** 567 * Returns a border from the defaults. If the value for {@code key} is not a 568 * {@code Border}, {@code defaultBorder} is returned. 569 * 570 * @param key 571 * an {@code Object} specifying the border 572 * @param defaultBorder 573 * the border to return if the border specified by 574 * {@code key} does not exist 575 * @return the {@code Border} object 576 * @throws NullPointerException 577 * if {@code key} or {@code defaultBorder} is {@code null} 578 */ 579 public static Border getSafeBorder(Object key, Border defaultBorder) { 580 Contract.asNotNull(defaultBorder, "defaultBorder cannot be null"); 581 582 Border safeBorder = UIManager.getBorder(key); 583 584 if (safeBorder == null) { 585 safeBorder = defaultBorder; 586 } 587 588 if (!(safeBorder instanceof UIResource)) { 589 safeBorder = new BorderUIResource(safeBorder); 590 } 591 592 return safeBorder; 593 } 594 595 /** 596 * Returns a color from the defaults. If the value for {@code key} is not a 597 * {@code Color}, {@code defaultColor} is returned. 598 * 599 * @param key 600 * an {@code Object} specifying the color 601 * @param defaultColor 602 * the color to return if the color specified by {@code key} 603 * does not exist 604 * @return the {@code Color} object 605 * @throws NullPointerException 606 * if {@code key} or {@code defaultColor} is {@code null} 607 */ 608 public static Color getSafeColor(Object key, Color defaultColor) { 609 Contract.asNotNull(defaultColor, "defaultColor cannot be null"); 610 611 Color safeColor = UIManager.getColor(key); 612 613 if (safeColor == null) { 614 safeColor = defaultColor; 615 } 616 617 if (!(safeColor instanceof UIResource)) { 618 safeColor = new ColorUIResource(safeColor); 619 } 620 621 return safeColor; 622 } 623 624 /** 625 * Returns a dimension from the defaults. If the value for {@code key} is 626 * not a {@code Dimension}, {@code defaultDimension} is returned. 627 * 628 * @param key 629 * an {@code Object} specifying the dimension 630 * @param defaultDimension 631 * the dimension to return if the dimension specified by 632 * {@code key} does not exist 633 * @return the {@code Dimension} object 634 * @throws NullPointerException 635 * if {@code key} or {@code defaultColor} is {@code null} 636 */ 637 public static Dimension getSafeDimension(Object key, Dimension defaultDimension) { 638 Contract.asNotNull(defaultDimension, "defaultDimension cannot be null"); 639 640 Dimension safeDimension = UIManager.getDimension(key); 641 642 if (safeDimension == null) { 643 safeDimension = defaultDimension; 644 } 645 646 if (!(safeDimension instanceof UIResource)) { 647 safeDimension = new DimensionUIResource(safeDimension.width, safeDimension.height); 648 } 649 650 return safeDimension; 651 } 652 653 /** 654 * Returns a font from the defaults. If the value for {@code key} is not a 655 * {@code Font}, {@code defaultFont} is returned. 656 * 657 * @param key 658 * an {@code Object} specifying the font 659 * @param defaultFont 660 * the font to return if the font specified by {@code key} 661 * does not exist 662 * @return the {@code Font} object 663 * @throws NullPointerException 664 * if {@code key} or {@code defaultFont} is {@code null} 665 */ 666 public static Font getSafeFont(Object key, Font defaultFont) { 667 Contract.asNotNull(defaultFont, "defaultFont cannot be null"); 668 669 Font safeFont = UIManager.getFont(key); 670 671 if (safeFont == null) { 672 safeFont = defaultFont; 673 } 674 675 if (!(safeFont instanceof UIResource)) { 676 safeFont = new FontUIResource(safeFont); 677 } 678 679 return safeFont; 680 } 681 682 /** 683 * Returns an icon from the defaults. If the value for {@code key} is not a 684 * {@code Icon}, {@code defaultIcon} is returned. 685 * 686 * @param key 687 * an {@code Object} specifying the icon 688 * @param defaultIcon 689 * the icon to return if the icon specified by {@code key} 690 * does not exist 691 * @return the {@code Icon} object 692 * @throws NullPointerException 693 * if {@code key} or {@code defaultIcon} is {@code null} 694 */ 695 public static Icon getSafeIcon(Object key, Icon defaultIcon) { 696 Contract.asNotNull(defaultIcon, "defaultIcon cannot be null"); 697 698 Icon safeIcon = UIManager.getIcon(key); 699 700 if (safeIcon == null) { 701 safeIcon = defaultIcon; 702 } 703 704 if (!(safeIcon instanceof UIResource)) { 705 safeIcon = new IconUIResource(safeIcon); 706 } 707 708 return safeIcon; 709 } 710 711 /** 712 * Returns an insets from the defaults. If the value for {@code key} is not 713 * a {@code Insets}, {@code defaultInsets} is returned. 714 * 715 * @param key 716 * an {@code Object} specifying the insets 717 * @param defaultInsets 718 * the insets to return if the insets specified by 719 * {@code key} does not exist 720 * @return the {@code Insets} object 721 * @throws NullPointerException 722 * if {@code key} or {@code defaultInsets} is {@code null} 723 */ 724 public static Insets getSafeInsets(Object key, Insets defaultInsets) { 725 Contract.asNotNull(defaultInsets, "defaultInsets cannot be null"); 726 727 Insets safeInsets = UIManager.getInsets(key); 728 729 if (safeInsets == null) { 730 safeInsets = defaultInsets; 731 } 732 733 if (!(safeInsets instanceof UIResource)) { 734 safeInsets = new InsetsUIResource(safeInsets.top, safeInsets.left, 735 safeInsets.bottom, safeInsets.right); 736 } 737 738 return safeInsets; 739 } 740}