001 /*
002 * $Id: LookAndFeelAddons.java,v 1.15 2005/12/10 11:33:36 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.aqua.AquaLookAndFeelAddons;
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 =
066 new ArrayList<ComponentAddon>();
067
068 /**
069 * Key used to ensure the current UIManager has been populated by the
070 * LookAndFeelAddons.
071 */
072 private static final Object APPCONTEXT_INITIALIZED = new Object();
073
074 private static boolean trackingChanges = false;
075 private static PropertyChangeListener changeListener;
076
077 static {
078 // load the default addon
079 String addonClassname = getBestMatchAddonClassName();
080 try {
081 addonClassname = System.getProperty("swing.addon", 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, List<Object> defaults) {
101 addResource(defaults, "org.jdesktop.swingx.plaf.resources.swingx");
102 }
103 });
104 }
105
106 private static LookAndFeelAddons currentAddon;
107
108 public void initialize() {
109 for (Iterator<ComponentAddon> iter = contributedComponents.iterator(); iter
110 .hasNext();) {
111 ComponentAddon addon = iter.next();
112 addon.initialize(this);
113 }
114 }
115
116 public void uninitialize() {
117 for (Iterator<ComponentAddon> iter = contributedComponents.iterator(); iter
118 .hasNext();) {
119 ComponentAddon addon = iter.next();
120 addon.uninitialize(this);
121 }
122 }
123
124 /**
125 * Adds the given defaults in UIManager.
126 *
127 * @param keysAndValues
128 */
129 public void loadDefaults(Object[] keysAndValues) {
130 for (int i = 0, c = keysAndValues.length; i < c; i = i + 2) {
131 UIManager.getLookAndFeelDefaults().put(keysAndValues[i], keysAndValues[i + 1]);
132 }
133 }
134
135 public void unloadDefaults(Object[] keysAndValues) {
136 for (int i = 0, c = keysAndValues.length; i < c; i = i + 2) {
137 UIManager.getLookAndFeelDefaults().put(keysAndValues[i], null);
138 }
139 }
140
141 public static void setAddon(String addonClassName)
142 throws InstantiationException, IllegalAccessException,
143 ClassNotFoundException {
144 setAddon(Class.forName(addonClassName));
145 }
146
147 public static void setAddon(Class addonClass) throws InstantiationException,
148 IllegalAccessException {
149 LookAndFeelAddons addon = (LookAndFeelAddons)addonClass.newInstance();
150 setAddon(addon);
151 }
152
153 public static void setAddon(LookAndFeelAddons addon) {
154 if (currentAddon != null) {
155 currentAddon.uninitialize();
156 }
157
158 addon.initialize();
159 currentAddon = addon;
160 UIManager.put(APPCONTEXT_INITIALIZED, Boolean.TRUE);
161 }
162
163 public static LookAndFeelAddons getAddon() {
164 return currentAddon;
165 }
166
167 /**
168 * Based on the current look and feel (as returned by
169 * <code>UIManager.getLookAndFeel()</code>), this method returns
170 * the name of the closest <code>LookAndFeelAddons</code> to use.
171 *
172 * @return the addon matching the currently installed look and feel
173 */
174 public static String getBestMatchAddonClassName() {
175 String lnf = UIManager.getLookAndFeel().getClass().getName();
176 String addon;
177 if (UIManager.getCrossPlatformLookAndFeelClassName().equals(lnf)) {
178 addon = MetalLookAndFeelAddons.class.getName();
179 } else if (UIManager.getSystemLookAndFeelClassName().equals(lnf)) {
180 addon = getSystemAddonClassName();
181 } else if ("com.sun.java.swing.plaf.windows.WindowsLookAndFeel".equals(lnf) ||
182 "com.jgoodies.looks.windows.WindowsLookAndFeel".equals(lnf)) {
183 if (OS.isUsingWindowsVisualStyles()) {
184 addon = WindowsLookAndFeelAddons.class.getName();
185 } else {
186 addon = WindowsClassicLookAndFeelAddons.class.getName();
187 }
188 } else if ("com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel"
189 .equals(lnf)) {
190 addon = WindowsClassicLookAndFeelAddons.class.getName();
191 } else if (UIManager.getLookAndFeel().getID().equals("Motif")) {
192 addon = MotifLookAndFeelAddons.class.getName();
193 } else {
194 addon = getSystemAddonClassName();
195 }
196 return addon;
197 }
198
199 /**
200 * Gets the addon best suited for the operating system where the
201 * virtual machine is running.
202 *
203 * @return the addon matching the native operating system platform.
204 */
205 public static String getSystemAddonClassName() {
206 String addon = WindowsClassicLookAndFeelAddons.class.getName();
207
208 if (OS.isMacOSX()) {
209 addon = AquaLookAndFeelAddons.class.getName();
210 } else if (OS.isWindows()) {
211 // see whether of not visual styles are used
212 if (OS.isUsingWindowsVisualStyles()) {
213 addon = WindowsLookAndFeelAddons.class.getName();
214 } else {
215 addon = WindowsClassicLookAndFeelAddons.class.getName();
216 }
217 }
218
219 return addon;
220 }
221
222 /**
223 * Each new component added by the library will contribute its
224 * default UI classes, colors and fonts to the LookAndFeelAddons.
225 * See {@link ComponentAddon}.
226 *
227 * @param component
228 */
229 public static void contribute(ComponentAddon component) {
230 contributedComponents.add(component);
231
232 if (currentAddon != null) {
233 // make sure to initialize any addons added after the
234 // LookAndFeelAddons has been installed
235 component.initialize(currentAddon);
236 }
237 }
238
239 /**
240 * Removes the contribution of the given addon
241 *
242 * @param component
243 */
244 public static void uncontribute(ComponentAddon component) {
245 contributedComponents.remove(component);
246
247 if (currentAddon != null) {
248 component.uninitialize(currentAddon);
249 }
250 }
251
252 /**
253 * Workaround for IDE mixing up with classloaders and Applets environments.
254 * Consider this method as API private. It must not be called directly.
255 *
256 * @param component
257 * @param expectedUIClass
258 * @return an instance of expectedUIClass
259 */
260 public static ComponentUI getUI(JComponent component, Class expectedUIClass) {
261 maybeInitialize();
262
263 // solve issue with ClassLoader not able to find classes
264 String uiClassname = (String)UIManager.get(component.getUIClassID());
265 try {
266 Class uiClass = Class.forName(uiClassname);
267 UIManager.put(uiClassname, uiClass);
268 } catch (ClassNotFoundException e) {
269 // we ignore the ClassNotFoundException
270 }
271
272 ComponentUI ui = UIManager.getUI(component);
273
274 if (expectedUIClass.isInstance(ui)) {
275 return ui;
276 } else {
277 String realUI = ui.getClass().getName();
278 Class realUIClass;
279 try {
280 realUIClass = expectedUIClass.getClassLoader()
281 .loadClass(realUI);
282 } catch (ClassNotFoundException e) {
283 throw new RuntimeException("Failed to load class " + realUI, e);
284 }
285 Method createUIMethod = null;
286 try {
287 createUIMethod = realUIClass.getMethod("createUI", new Class[]{JComponent.class});
288 } catch (NoSuchMethodException e1) {
289 throw new RuntimeException("Class " + realUI + " has no method createUI(JComponent)");
290 }
291 try {
292 return (ComponentUI)createUIMethod.invoke(null, new Object[]{component});
293 } catch (Exception e2) {
294 throw new RuntimeException("Failed to invoke " + realUI + "#createUI(JComponent)");
295 }
296 }
297 }
298
299 /**
300 * With applets, if you reload the current applet, the UIManager will be
301 * reinitialized (entries previously added by LookAndFeelAddons will be
302 * removed) but the addon will not reinitialize because addon initialize
303 * itself through the static block in components and the classes do not get
304 * reloaded. This means component.updateUI will fail because it will not find
305 * its UI.
306 *
307 * This method ensures LookAndFeelAddons get re-initialized if needed. It must
308 * be called in every component updateUI methods.
309 */
310 private static synchronized void maybeInitialize() {
311 if (currentAddon != null) {
312 // this is to ensure "UIManager#maybeInitialize" gets called and the
313 // LAFState initialized
314 UIManager.getLookAndFeelDefaults();
315
316 if (!UIManager.getBoolean(APPCONTEXT_INITIALIZED)) {
317 setAddon(currentAddon);
318 }
319 }
320 }
321
322 //
323 // TRACKING OF THE CURRENT LOOK AND FEEL
324 //
325 private static class UpdateAddon implements PropertyChangeListener {
326 public void propertyChange(PropertyChangeEvent evt) {
327 try {
328 setAddon(getBestMatchAddonClassName());
329 } catch (Exception e) {
330 // should not happen
331 throw new RuntimeException(e);
332 }
333 }
334 }
335
336 /**
337 * If true, everytime the Swing look and feel is changed, the addon which
338 * best matches the current look and feel will be automatically selected.
339 *
340 * @param tracking
341 * true to automatically update the addon, false to not automatically
342 * track the addon. Defaults to false.
343 * @see #getBestMatchAddonClassName()
344 */
345 public static synchronized void setTrackingLookAndFeelChanges(boolean tracking) {
346 if (trackingChanges != tracking) {
347 if (tracking) {
348 if (changeListener == null) {
349 changeListener = new UpdateAddon();
350 }
351 UIManager.addPropertyChangeListener(changeListener);
352 } else {
353 if (changeListener != null) {
354 UIManager.removePropertyChangeListener(changeListener);
355 }
356 changeListener = null;
357 }
358 trackingChanges = tracking;
359 }
360 }
361
362 /**
363 * @return true if the addon will be automatically change to match the current
364 * look and feel
365 * @see #setTrackingLookAndFeelChanges(boolean)
366 */
367 public static synchronized boolean isTrackingLookAndFeelChanges() {
368 return trackingChanges;
369 }
370
371 }