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 }