001: /*
002: * $Id: LookAndFeelAddons.java,v 1.19 2007/02/07 15:09:51 l2fprod Exp $
003: *
004: * Copyright 2004 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.beans.PropertyChangeEvent;
024: import java.beans.PropertyChangeListener;
025: import java.lang.reflect.Method;
026: import java.util.ArrayList;
027: import java.util.Iterator;
028: import java.util.List;
029:
030: import javax.swing.JComponent;
031: import javax.swing.UIManager;
032: import javax.swing.plaf.ComponentUI;
033:
034: import org.jdesktop.swingx.plaf.macosx.MacOSXLookAndFeelAddons;
035: import org.jdesktop.swingx.plaf.metal.MetalLookAndFeelAddons;
036: import org.jdesktop.swingx.plaf.motif.MotifLookAndFeelAddons;
037: import org.jdesktop.swingx.plaf.windows.WindowsClassicLookAndFeelAddons;
038: import org.jdesktop.swingx.plaf.windows.WindowsLookAndFeelAddons;
039: import org.jdesktop.swingx.util.OS;
040:
041: /**
042: * Provides additional pluggable UI for new components added by the
043: * library. By default, the library uses the pluggable UI returned by
044: * {@link #getBestMatchAddonClassName()}.
045: * <p>
046: * The default addon can be configured using the
047: * <code>swing.addon</code> system property as follow:
048: * <ul>
049: * <li>on the command line,
050: * <code>java -Dswing.addon=ADDONCLASSNAME ...</code></li>
051: * <li>at runtime and before using the library components
052: * <code>System.getProperties().put("swing.addon", ADDONCLASSNAME);</code>
053: * </li>
054: * </ul>
055: * <p>
056: * The addon can also be installed directly by calling the
057: * {@link #setAddon(String)}method. For example, to install the
058: * Windows addons, add the following statement
059: * <code>LookAndFeelAddons.setAddon("org.jdesktop.swingx.plaf.windows.WindowsLookAndFeelAddons");</code>.
060: *
061: * @author <a href="mailto:fred@L2FProd.com">Frederic Lavigne</a>
062: */
063: public class LookAndFeelAddons {
064:
065: private static List<ComponentAddon> contributedComponents = new ArrayList<ComponentAddon>();
066:
067: /**
068: * Key used to ensure the current UIManager has been populated by the
069: * LookAndFeelAddons.
070: */
071: private static final Object APPCONTEXT_INITIALIZED = new Object();
072:
073: private static boolean trackingChanges = false;
074: private static PropertyChangeListener changeListener;
075:
076: static {
077: // load the default addon
078: String addonClassname = getBestMatchAddonClassName();
079: try {
080: addonClassname = System.getProperty("swing.addon",
081: addonClassname);
082: } catch (SecurityException e) {
083: // security exception may arise in Java Web Start
084: }
085:
086: try {
087: setAddon(addonClassname);
088: } catch (Exception e) {
089: // PENDING(fred) do we want to log an error and continue with a default
090: // addon class or do we just fail?
091: throw new ExceptionInInitializerError(e);
092: }
093:
094: setTrackingLookAndFeelChanges(true);
095:
096: // this addon ensure resource bundle gets added to lookandfeel defaults
097: // and re-added by #maybeInitialize if needed
098: contribute(new AbstractComponentAddon("MinimumAddon") {
099: @Override
100: protected void addBasicDefaults(LookAndFeelAddons addon,
101: List<Object> defaults) {
102: addResource(defaults,
103: "org.jdesktop.swingx.plaf.resources.swingx");
104: }
105: });
106: }
107:
108: private static LookAndFeelAddons currentAddon;
109:
110: public void initialize() {
111: for (Iterator<ComponentAddon> iter = contributedComponents
112: .iterator(); iter.hasNext();) {
113: ComponentAddon addon = iter.next();
114: addon.initialize(this );
115: }
116: }
117:
118: public void uninitialize() {
119: for (Iterator<ComponentAddon> iter = contributedComponents
120: .iterator(); iter.hasNext();) {
121: ComponentAddon addon = iter.next();
122: addon.uninitialize(this );
123: }
124: }
125:
126: /**
127: * Adds the given defaults in UIManager.
128: *
129: * Note: the values are added only if they do not exist in the existing look
130: * and feel defaults. This makes it possible for look and feel implementors to
131: * override SwingX defaults.
132: *
133: * Note: the array is traversed in reverse order. If a key is found twice in
134: * the array, the key/value with the highest position in the array gets
135: * precedence over the other key in the array
136: *
137: * @param keysAndValues
138: */
139: public void loadDefaults(Object[] keysAndValues) {
140: // Go in reverse order so the most recent keys get added first...
141: for (int i = keysAndValues.length - 2; i >= 0; i = i - 2) {
142: if (UIManager.getLookAndFeelDefaults()
143: .get(keysAndValues[i]) == null) {
144: UIManager.getLookAndFeelDefaults().put(
145: keysAndValues[i], keysAndValues[i + 1]);
146: }
147: }
148: }
149:
150: public void unloadDefaults(Object[] keysAndValues) {
151: // commented after Issue 446.
152: /*
153: for (int i = 0, c = keysAndValues.length; i < c; i = i + 2) {
154: UIManager.getLookAndFeelDefaults().put(keysAndValues[i], null);
155: }
156: */
157: }
158:
159: public static void setAddon(String addonClassName)
160: throws InstantiationException, IllegalAccessException,
161: ClassNotFoundException {
162: setAddon(Class.forName(addonClassName));
163: }
164:
165: public static void setAddon(Class addonClass)
166: throws InstantiationException, IllegalAccessException {
167: LookAndFeelAddons addon = (LookAndFeelAddons) addonClass
168: .newInstance();
169: setAddon(addon);
170: }
171:
172: public static void setAddon(LookAndFeelAddons addon) {
173: if (currentAddon != null) {
174: currentAddon.uninitialize();
175: }
176:
177: addon.initialize();
178: currentAddon = addon;
179: UIManager.put(APPCONTEXT_INITIALIZED, Boolean.TRUE);
180: }
181:
182: public static LookAndFeelAddons getAddon() {
183: return currentAddon;
184: }
185:
186: /**
187: * Based on the current look and feel (as returned by
188: * <code>UIManager.getLookAndFeel()</code>), this method returns
189: * the name of the closest <code>LookAndFeelAddons</code> to use.
190: *
191: * @return the addon matching the currently installed look and feel
192: */
193: public static String getBestMatchAddonClassName() {
194: String lnf = UIManager.getLookAndFeel().getClass().getName();
195: String addon;
196: if (UIManager.getCrossPlatformLookAndFeelClassName()
197: .equals(lnf)) {
198: addon = MetalLookAndFeelAddons.class.getName();
199: } else if (UIManager.getSystemLookAndFeelClassName()
200: .equals(lnf)) {
201: addon = getSystemAddonClassName();
202: } else if ("com.sun.java.swing.plaf.windows.WindowsLookAndFeel"
203: .equals(lnf)
204: || "com.jgoodies.looks.windows.WindowsLookAndFeel"
205: .equals(lnf)) {
206: if (OS.isUsingWindowsVisualStyles()) {
207: addon = WindowsLookAndFeelAddons.class.getName();
208: } else {
209: addon = WindowsClassicLookAndFeelAddons.class.getName();
210: }
211: } else if ("com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel"
212: .equals(lnf)) {
213: addon = WindowsClassicLookAndFeelAddons.class.getName();
214: } else if (UIManager.getLookAndFeel().getID().equals("Motif")) {
215: addon = MotifLookAndFeelAddons.class.getName();
216: } else {
217: addon = getSystemAddonClassName();
218: }
219: return addon;
220: }
221:
222: /**
223: * Gets the addon best suited for the operating system where the
224: * virtual machine is running.
225: *
226: * @return the addon matching the native operating system platform.
227: */
228: public static String getSystemAddonClassName() {
229: String addon = WindowsClassicLookAndFeelAddons.class.getName();
230:
231: if (OS.isMacOSX()) {
232: addon = MacOSXLookAndFeelAddons.class.getName();
233: } else if (OS.isWindows()) {
234: // see whether of not visual styles are used
235: if (OS.isUsingWindowsVisualStyles()) {
236: addon = WindowsLookAndFeelAddons.class.getName();
237: } else {
238: addon = WindowsClassicLookAndFeelAddons.class.getName();
239: }
240: }
241:
242: return addon;
243: }
244:
245: /**
246: * Each new component added by the library will contribute its
247: * default UI classes, colors and fonts to the LookAndFeelAddons.
248: * See {@link ComponentAddon}.
249: *
250: * @param component
251: */
252: public static void contribute(ComponentAddon component) {
253: contributedComponents.add(component);
254:
255: if (currentAddon != null) {
256: // make sure to initialize any addons added after the
257: // LookAndFeelAddons has been installed
258: component.initialize(currentAddon);
259: }
260: }
261:
262: /**
263: * Removes the contribution of the given addon
264: *
265: * @param component
266: */
267: public static void uncontribute(ComponentAddon component) {
268: contributedComponents.remove(component);
269:
270: if (currentAddon != null) {
271: component.uninitialize(currentAddon);
272: }
273: }
274:
275: /**
276: * Workaround for IDE mixing up with classloaders and Applets environments.
277: * Consider this method as API private. It must not be called directly.
278: *
279: * @param component
280: * @param expectedUIClass
281: * @return an instance of expectedUIClass
282: */
283: public static ComponentUI getUI(JComponent component,
284: Class expectedUIClass) {
285: maybeInitialize();
286:
287: // solve issue with ClassLoader not able to find classes
288: String uiClassname = (String) UIManager.get(component
289: .getUIClassID());
290: try {
291: Class uiClass = Class.forName(uiClassname);
292: UIManager.put(uiClassname, uiClass);
293: } catch (ClassNotFoundException e) {
294: // we ignore the ClassNotFoundException
295: }
296:
297: ComponentUI ui = UIManager.getUI(component);
298:
299: if (expectedUIClass.isInstance(ui)) {
300: return ui;
301: } else {
302: String realUI = ui.getClass().getName();
303: Class realUIClass;
304: try {
305: realUIClass = expectedUIClass.getClassLoader()
306: .loadClass(realUI);
307: } catch (ClassNotFoundException e) {
308: throw new RuntimeException("Failed to load class "
309: + realUI, e);
310: }
311: Method createUIMethod = null;
312: try {
313: createUIMethod = realUIClass.getMethod("createUI",
314: new Class[] { JComponent.class });
315: } catch (NoSuchMethodException e1) {
316: throw new RuntimeException("Class " + realUI
317: + " has no method createUI(JComponent)");
318: }
319: try {
320: return (ComponentUI) createUIMethod.invoke(null,
321: new Object[] { component });
322: } catch (Exception e2) {
323: throw new RuntimeException("Failed to invoke " + realUI
324: + "#createUI(JComponent)");
325: }
326: }
327: }
328:
329: /**
330: * With applets, if you reload the current applet, the UIManager will be
331: * reinitialized (entries previously added by LookAndFeelAddons will be
332: * removed) but the addon will not reinitialize because addon initialize
333: * itself through the static block in components and the classes do not get
334: * reloaded. This means component.updateUI will fail because it will not find
335: * its UI.
336: *
337: * This method ensures LookAndFeelAddons get re-initialized if needed. It must
338: * be called in every component updateUI methods.
339: */
340: private static synchronized void maybeInitialize() {
341: if (currentAddon != null) {
342: // this is to ensure "UIManager#maybeInitialize" gets called and the
343: // LAFState initialized
344: UIManager.getLookAndFeelDefaults();
345:
346: if (!UIManager.getBoolean(APPCONTEXT_INITIALIZED)) {
347: setAddon(currentAddon);
348: }
349: }
350: }
351:
352: //
353: // TRACKING OF THE CURRENT LOOK AND FEEL
354: //
355: private static class UpdateAddon implements PropertyChangeListener {
356: public void propertyChange(PropertyChangeEvent evt) {
357: try {
358: setAddon(getBestMatchAddonClassName());
359: } catch (Exception e) {
360: // should not happen
361: throw new RuntimeException(e);
362: }
363: }
364: }
365:
366: /**
367: * If true, everytime the Swing look and feel is changed, the addon which
368: * best matches the current look and feel will be automatically selected.
369: *
370: * @param tracking
371: * true to automatically update the addon, false to not automatically
372: * track the addon. Defaults to false.
373: * @see #getBestMatchAddonClassName()
374: */
375: public static synchronized void setTrackingLookAndFeelChanges(
376: boolean tracking) {
377: if (trackingChanges != tracking) {
378: if (tracking) {
379: if (changeListener == null) {
380: changeListener = new UpdateAddon();
381: }
382: UIManager.addPropertyChangeListener(changeListener);
383: } else {
384: if (changeListener != null) {
385: UIManager
386: .removePropertyChangeListener(changeListener);
387: }
388: changeListener = null;
389: }
390: trackingChanges = tracking;
391: }
392: }
393:
394: /**
395: * @return true if the addon will be automatically change to match the current
396: * look and feel
397: * @see #setTrackingLookAndFeelChanges(boolean)
398: */
399: public static synchronized boolean isTrackingLookAndFeelChanges() {
400: return trackingChanges;
401: }
402:
403: }
|