/* * $Id: UIManagerExt.java 3472 2009-08-27 13:12:42Z kleopatra $ * * Copyright 2007 Sun Microsystems, Inc., 4150 Network Circle, * Santa Clara, California 95054, U.S.A. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.hdesktop.swingx.plaf; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Insets; import java.awt.Shape; import java.util.Enumeration; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; import java.util.ResourceBundle; import java.util.Vector; import javax.swing.Icon; import javax.swing.UIDefaults; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.plaf.BorderUIResource; import javax.swing.plaf.ColorUIResource; import javax.swing.plaf.DimensionUIResource; import javax.swing.plaf.FontUIResource; import javax.swing.plaf.IconUIResource; import javax.swing.plaf.InsetsUIResource; import javax.swing.plaf.UIResource; import org.hdesktop.swingx.painter.Painter; import org.hdesktop.swingx.util.Contract; /** * A utility class for obtaining configuration properties from the * {@code UIDefaults}. This class handles SwingX-specific L&F needs, such as * the installation of painters and shapes. There are several categories of * utility methods: * <ul> * <li>Support for the safe creation of {@code UIResource}s.</li> * <li>Support for new {@code UIResource} types, such as * {@code PainterUIResource}.</li> * <li>Support for the dynamic localization of {@code UIDefaults}.</li> * <li>Support for returning non-{@code String} localizations from * {@code ResourceBundle}s.</li> * </ul> * <h3>Safe Methods</h3> * <p> * The {@code getSafeXXX} methods are designed for use with * {@code LookAndFeelAddon}s. Any addon that attempts to obtain a property * defined in the defaults (available from {@code UIManager.get}) to set a * property that will be added to the defaults for the addon should use the * "safe" methods. The methods ensure that a valid value is always returned and * that value is a {@code UIResource}. * </p> * <h3>Support for New Types</h3> * <p> * {@code UIManagerExt} supports the retrieval of new {@code UIResource} types. * There is a {@code getXXX} method for every {@code UIResource} subtype in the * {@code org.hdesktop.swingx.plaf} package. * </p> * <h3>Support for Dynamic Localization</h3> * <p> * {@code UIManagerExt} enables dynamic localization by supporting * {@code ResourceBundle}s. The * {@linkplain UIDefaults#addResourceBundle(String)} allows resource bundles to * be added to the {@code UIDefaults}. While there is support for this feature * in core, there is a bug with the class loader that prevents user added * bundles from working correctly when used via Web Start. Therefore, * {@code UIManagerExt} defines methods to add and remove resource bundles. * These are the only methods that SwingX classes should use when adding * resource bundles to the defaults. Since {@code UIManagerExt} is maintaining * the bundles, any localized {@code String}s <b>must</b> be retrieved from * the {@code getString} methods in this class. * </p> * <h3>Support for Non-{@code String} Localization Values</h3> * <p> * All methods work by first determining if the value is present * {@code UIDefaults}. If the value is not present, then the installed * {@code ResourceBundle}s are queried. {@code UIManagerExt} will attempt to * convert any returned value to the appropriate type. For instance, * {@code getInt} uses {@code Integer.decode} to convert {@code String}s * returned from the bundle into {@code int}s. * </p> * * @author Karl George Schaefer * * @see UIManager * @see UIDefaults */ public class UIManagerExt { /** * Used to replicate the resource bundle behavior from the * {@code UIDefaults}. */ private static class UIDefaultsExt { //use vector; we want synchronization private Vector<String> resourceBundles; /** * Maps from a Locale to a cached Map of the ResourceBundle. This is done * so as to avoid an exception being thrown when a value is asked for. * Access to this should be done while holding a lock on the * UIDefaults, eg synchronized(this). */ private Map<Locale, Map<String, String>> resourceCache; UIDefaultsExt() { resourceCache = new HashMap<Locale, Map<String,String>>(); } //should this just return String? private Object getFromResourceBundle(Object key, Locale l) { if( resourceBundles == null || resourceBundles.isEmpty() || !(key instanceof String) ) { return null; } // A null locale means use the default locale. if( l == null ) { l = Locale.getDefault(); } synchronized(this) { return getResourceCache(l).get((String)key); } } /** * Returns a Map of the known resources for the given locale. */ private Map<String, String> getResourceCache(Locale l) { Map<String, String> values = (Map<String, String>) resourceCache.get(l); if (values == null) { values = new HashMap<String, String>(); for (int i=resourceBundles.size()-1; i >= 0; i--) { String bundleName = (String)resourceBundles.get(i); try { ResourceBundle b = ResourceBundle. getBundle(bundleName, l, UIManagerExt.class.getClassLoader()); Enumeration<String> keys = b.getKeys(); while (keys.hasMoreElements()) { String key = (String)keys.nextElement(); if (values.get(key) == null) { Object value = b.getObject(key); values.put(key, (String) value); } } } catch( MissingResourceException mre ) { // Keep looking } } resourceCache.put(l, values); } return values; } public synchronized void addResourceBundle(String bundleName) { if( bundleName == null ) { return; } if( resourceBundles == null ) { resourceBundles = new Vector<String>(5); } if (!resourceBundles.contains(bundleName)) { resourceBundles.add( bundleName ); resourceCache.clear(); } } public synchronized void removeResourceBundle( String bundleName ) { if( resourceBundles != null ) { resourceBundles.remove( bundleName ); } resourceCache.clear(); } } private static UIDefaultsExt uiDefaultsExt = new UIDefaultsExt(); private UIManagerExt() { //does nothing } /** * Adds a resource bundle to the list of resource bundles that are searched * for localized values. Resource bundles are searched in the reverse order * they were added. In other words, the most recently added bundle is * searched first. * * @param bundleName * the base name of the resource bundle to be added * @see java.util.ResourceBundle * @see #removeResourceBundle */ public static void addResourceBundle(String bundleName) { uiDefaultsExt.addResourceBundle(bundleName); } /** * Removes a resource bundle from the list of resource bundles that are * searched for localized defaults. * * @param bundleName * the base name of the resource bundle to be removed * @see java.util.ResourceBundle * @see #addResourceBundle */ public static void removeResourceBundle(String bundleName) { uiDefaultsExt.removeResourceBundle(bundleName); } /** * Returns a string from the defaults. If the value for {@code key} is not a * {@code String}, {@code null} is returned. * * @param key * an {@code Object} specifying the string * @return the {@code String} object * @throws NullPointerException * if {@code key} is {@code null} */ public static String getString(Object key) { return getString(key, null); } /** * Returns a string from the defaults. If the value for {@code key} is not a * {@code String}, {@code null} is returned. * * @param key * an {@code Object} specifying the string * @param l * the {@code Locale} for which the painter is desired; refer * to {@code UIDefaults} for details on how a {@code null} * {@code Locale} is handled * @return the {@code String} object * @throws NullPointerException * if {@code key} is {@code null} */ public static String getString(Object key, Locale l) { Object value = UIManager.get(key, l); if (value instanceof String) { return (String) value; } //only return resource bundle if not in UIDefaults if (value == null) { value = uiDefaultsExt.getFromResourceBundle(key, l); if (value instanceof String) { return (String) value; } } return null; } /** * Returns an integer from the defaults. If the value for {@code key} is not * an {@code int}, {@code 0} is returned. * * @param key * an {@code Object} specifying the integer * @return the {@code int} * @throws NullPointerException * if {@code key} is {@code null} */ public static int getInt(Object key) { return getInt(key, null); } /** * Returns an integer from the defaults. If the value for {@code key} is not * an {@code int}, {@code 0} is returned. * * @param key * an {@code Object} specifying the integer * @param l * the {@code Locale} for which the integer is desired; refer * to {@code UIDefaults} for details on how a {@code null} * {@code Locale} is handled * @return the {@code int} * @throws NullPointerException * if {@code key} is {@code null} */ public static int getInt(Object key, Locale l) { Object value = UIManager.get(key, l); if (value instanceof Integer) { return (Integer) value; } if (value == null) { value = uiDefaultsExt.getFromResourceBundle(key, l); if (value instanceof Integer) { return (Integer) value; } if (value instanceof String) { try { return Integer.decode((String) value); } catch (NumberFormatException e) { // ignore - the entry was not parseable, can't do anything // JW: should we log it? } } } return 0; } /** * Returns an Boolean from the defaults. If the value for {@code key} is not * a {@code boolean}, {@code false} is returned. * * @param key * an {@code Object} specifying the Boolean * @return the {@code boolean} * @throws NullPointerException * if {@code key} is {@code null} */ public static boolean getBoolean(Object key) { return getBoolean(key, null); } /** * Returns an Boolean from the defaults. If the value for {@code key} is not * a {@code boolean}, {@code false} is returned. * * @param key * an {@code Object} specifying the Boolean * @param l * the {@code Locale} for which the Boolean is desired; refer * to {@code UIDefaults} for details on how a {@code null} * {@code Locale} is handled * @return the {@code boolean} * @throws NullPointerException * if {@code key} is {@code null} */ public static boolean getBoolean(Object key, Locale l) { Object value = UIManager.get(key, l); if (value instanceof Boolean) { return (Boolean) value; } //only return resource bundle if not in UIDefaults if (value == null) { value = uiDefaultsExt.getFromResourceBundle(key, l); if (value instanceof Boolean) { return (Boolean) value; } if (value instanceof String) { return Boolean.valueOf((String) value); } } return false; } /** * Returns a color from the defaults. If the value for {@code key} is not * a {@code Color}, {@code null} is returned. * * @param key * an {@code Object} specifying the color * @return the {@code Color} object * @throws NullPointerException * if {@code key} is {@code null} */ public static Color getColor(Object key) { return getColor(key, null); } /** * Returns a color from the defaults. If the value for {@code key} is not * a {@code Color}, {@code null} is returned. * * @param key * an {@code Object} specifying the color * @param l * the {@code Locale} for which the color is desired; refer * to {@code UIDefaults} for details on how a {@code null} * {@code Locale} is handled * @return the {@code Color} object * @throws NullPointerException * if {@code key} is {@code null} */ public static Color getColor(Object key, Locale l) { Object value = UIManager.get(key, l); if (value instanceof Color) { return (Color) value; } //only return resource bundle if not in UIDefaults if (value == null) { value = uiDefaultsExt.getFromResourceBundle(key, l); if (value instanceof Color) { return (Color) value; } if (value instanceof String) { try { return Color.decode((String) value); } catch (NumberFormatException e) { // incorrect format; does nothing } } } return null; } //TODO: Font.decode always returns a valid font. This is not acceptable for UIManager // /** // * Returns a font from the defaults. If the value for {@code key} is not // * a {@code Font}, {@code null} is returned. // * // * @param key // * an {@code Object} specifying the font // * @return the {@code Font} object // * @throws NullPointerException // * if {@code key} is {@code null} // */ // public static Font getFont(Object key) { // return getFont(key, null); // } // // /** // * Returns a font from the defaults. If the value for {@code key} is not // * a {@code Font}, {@code null} is returned. // * // * @param key // * an {@code Object} specifying the font // * @param l // * the {@code Locale} for which the font is desired; refer // * to {@code UIDefaults} for details on how a {@code null} // * {@code Locale} is handled // * @return the {@code Font} object // * @throws NullPointerException // * if {@code key} is {@code null} // */ // public static Font getFont(Object key, Locale l) { // Object value = UIManager.get(key, l); // // if (value instanceof Font) { // return (Font) value; // } // // //only return resource bundle if not in UIDefaults // if (value == null) { // value = uiDefaultsExt.getFromResourceBundle(key, l); // // if (value instanceof Font) { // return (Font) value; // } // // if (value instanceof String) { // return Font.decode((String) value); // } // } // // return null; // } /** * Returns a shape from the defaults. If the value for {@code key} is not a * {@code Shape}, {@code null} is returned. * * @param key an {@code Object} specifying the shape * @return the {@code Shape} object * @throws NullPointerException if {@code key} is {@code null} */ public static Shape getShape(Object key) { Object value = UIManager.getDefaults().get(key); return (value instanceof Shape) ? (Shape) value : null; } /** * Returns a shape from the defaults that is appropriate for the given * locale. If the value for {@code key} is not a {@code Shape}, * {@code null} is returned. * * @param key * an {@code Object} specifying the shape * @param l * the {@code Locale} for which the shape is desired; refer * to {@code UIDefaults} for details on how a {@code null} * {@code Locale} is handled * @return the {@code Shape} object * @throws NullPointerException * if {@code key} is {@code null} */ public static Shape getShape(Object key, Locale l) { Object value = UIManager.getDefaults().get(key, l); return (value instanceof Shape) ? (Shape) value : null; } /** * Returns a painter from the defaults. If the value for {@code key} is not * a {@code Painter}, {@code null} is returned. * * @param key * an {@code Object} specifying the painter * @return the {@code Painter} object * @throws NullPointerException * if {@code key} is {@code null} */ public static Painter<?> getPainter(Object key) { Object value = UIManager.getDefaults().get(key); return (value instanceof Painter<?>) ? (Painter<?>) value : null; } /** * Returns a painter from the defaults that is appropriate for the given * locale. If the value for {@code key} is not a {@code Painter}, * {@code null} is returned. * * @param key * an {@code Object} specifying the painter * @param l * the {@code Locale} for which the painter is desired; refer * to {@code UIDefaults} for details on how a {@code null} * {@code Locale} is handled * @return the {@code Painter} object * @throws NullPointerException * if {@code key} is {@code null} */ public static Painter<?> getPainter(Object key, Locale l) { Object value = UIManager.getDefaults().get(key, l); return (value instanceof Painter<?>) ? (Painter<?>) value : null; } /** * Returns a border from the defaults. If the value for {@code key} is not a * {@code Border}, {@code defaultBorder} is returned. * * @param key * an {@code Object} specifying the border * @param defaultBorder * the border to return if the border specified by * {@code key} does not exist * @return the {@code Border} object * @throws NullPointerException * if {@code key} or {@code defaultBorder} is {@code null} */ public static Border getSafeBorder(Object key, Border defaultBorder) { Contract.asNotNull(defaultBorder, "defaultBorder cannot be null"); Border safeBorder = UIManager.getBorder(key); if (safeBorder == null) { safeBorder = defaultBorder; } if (!(safeBorder instanceof UIResource)) { safeBorder = new BorderUIResource(safeBorder); } return safeBorder; } /** * Returns a color from the defaults. If the value for {@code key} is not a * {@code Color}, {@code defaultColor} is returned. * * @param key * an {@code Object} specifying the color * @param defaultColor * the color to return if the color specified by {@code key} * does not exist * @return the {@code Color} object * @throws NullPointerException * if {@code key} or {@code defaultColor} is {@code null} */ public static Color getSafeColor(Object key, Color defaultColor) { Contract.asNotNull(defaultColor, "defaultColor cannot be null"); Color safeColor = UIManager.getColor(key); if (safeColor == null) { safeColor = defaultColor; } if (!(safeColor instanceof UIResource)) { safeColor = new ColorUIResource(safeColor); } return safeColor; } /** * Returns a dimension from the defaults. If the value for {@code key} is * not a {@code Dimension}, {@code defaultDimension} is returned. * * @param key * an {@code Object} specifying the dimension * @param defaultDimension * the dimension to return if the dimension specified by * {@code key} does not exist * @return the {@code Dimension} object * @throws NullPointerException * if {@code key} or {@code defaultColor} is {@code null} */ public static Dimension getSafeDimension(Object key, Dimension defaultDimension) { Contract.asNotNull(defaultDimension, "defaultDimension cannot be null"); Dimension safeDimension = UIManager.getDimension(key); if (safeDimension == null) { safeDimension = defaultDimension; } if (!(safeDimension instanceof UIResource)) { safeDimension = new DimensionUIResource(safeDimension.width, safeDimension.height); } return safeDimension; } /** * Returns a font from the defaults. If the value for {@code key} is not a * {@code Font}, {@code defaultFont} is returned. * * @param key * an {@code Object} specifying the font * @param defaultFont * the font to return if the font specified by {@code key} * does not exist * @return the {@code Font} object * @throws NullPointerException * if {@code key} or {@code defaultFont} is {@code null} */ public static Font getSafeFont(Object key, Font defaultFont) { Contract.asNotNull(defaultFont, "defaultFont cannot be null"); Font safeFont = UIManager.getFont(key); if (safeFont == null) { safeFont = defaultFont; } if (!(safeFont instanceof UIResource)) { safeFont = new FontUIResource(safeFont); } return safeFont; } /** * Returns an icon from the defaults. If the value for {@code key} is not a * {@code Icon}, {@code defaultIcon} is returned. * * @param key * an {@code Object} specifying the icon * @param defaultIcon * the icon to return if the icon specified by {@code key} * does not exist * @return the {@code Icon} object * @throws NullPointerException * if {@code key} or {@code defaultIcon} is {@code null} */ public static Icon getSafeIcon(Object key, Icon defaultIcon) { Contract.asNotNull(defaultIcon, "defaultIcon cannot be null"); Icon safeIcon = UIManager.getIcon(key); if (safeIcon == null) { safeIcon = defaultIcon; } if (!(safeIcon instanceof UIResource)) { safeIcon = new IconUIResource(safeIcon); } return safeIcon; } /** * Returns an insets from the defaults. If the value for {@code key} is not * a {@code Insets}, {@code defaultInsets} is returned. * * @param key * an {@code Object} specifying the insets * @param defaultInsets * the insets to return if the insets specified by * {@code key} does not exist * @return the {@code Insets} object * @throws NullPointerException * if {@code key} or {@code defaultInsets} is {@code null} */ public static Insets getSafeInsets(Object key, Insets defaultInsets) { Contract.asNotNull(defaultInsets, "defaultInsets cannot be null"); Insets safeInsets = UIManager.getInsets(key); if (safeInsets == null) { safeInsets = defaultInsets; } if (!(safeInsets instanceof UIResource)) { safeInsets = new InsetsUIResource(safeInsets.top, safeInsets.left, safeInsets.bottom, safeInsets.right); } return safeInsets; } }