001 /* 002 * $Id: SwingXUtilities.java 3359 2009-06-12 18:37:03Z kschaefe $ 003 * 004 * Copyright 2008 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; 022 023 import java.awt.Color; 024 import java.awt.Component; 025 import java.awt.Container; 026 import java.awt.Font; 027 import java.awt.Frame; 028 import java.awt.Window; 029 import java.awt.event.InputEvent; 030 import java.io.IOException; 031 import java.io.StringReader; 032 import java.lang.reflect.Method; 033 import java.util.Locale; 034 035 import javax.swing.InputMap; 036 import javax.swing.JComponent; 037 import javax.swing.JPopupMenu; 038 import javax.swing.KeyStroke; 039 import javax.swing.MenuElement; 040 import javax.swing.RepaintManager; 041 import javax.swing.SwingUtilities; 042 import javax.swing.plaf.ComponentInputMapUIResource; 043 import javax.swing.plaf.UIResource; 044 import javax.swing.text.html.HTMLDocument; 045 046 /** 047 * A collection of utility methods for Swing(X) classes. 048 * 049 * <ul> 050 * PENDING JW: think about location of this class and/or its methods, Options: 051 * 052 * <li> move this class to the swingx utils package which already has a bunch of xxUtils 053 * <li> move methods between xxUtils classes as appropriate (one window/comp related util) 054 * <li> keep here in swingx (consistent with swingutilities in core) 055 * </ul> 056 * @author Karl George Schaefer 057 */ 058 public final class SwingXUtilities { 059 private SwingXUtilities() { 060 //does nothing 061 } 062 063 064 /** 065 * A helper for creating and updating key bindings for components with 066 * mnemonics. The {@code pressed} action will be invoked when the mnemonic 067 * is activated. 068 * <p> 069 * TODO establish an interface for the mnemonic properties, such as {@code 070 * MnemonicEnabled} and change signature to {@code public static <T extends 071 * JComponent & MnemonicEnabled> void updateMnemonicBinding(T c, String 072 * pressed)} 073 * 074 * @param c 075 * the component bindings to update 076 * @param pressed 077 * the name of the action in the action map to invoke when the 078 * mnemonic is pressed 079 * @throws NullPointerException 080 * if the component is {@code null} 081 */ 082 public static void updateMnemonicBinding(JComponent c, String pressed) { 083 updateMnemonicBinding(c, pressed, null); 084 } 085 086 /** 087 * A helper for creating and updating key bindings for components with 088 * mnemonics. The {@code pressed} action will be invoked when the mnemonic 089 * is activated and the {@code released} action will be invoked when the 090 * mnemonic is deactivated. 091 * <p> 092 * TODO establish an interface for the mnemonic properties, such as {@code 093 * MnemonicEnabled} and change signature to {@code public static <T extends 094 * JComponent & MnemonicEnabled> void updateMnemonicBinding(T c, String 095 * pressed, String released)} 096 * 097 * @param c 098 * the component bindings to update 099 * @param pressed 100 * the name of the action in the action map to invoke when the 101 * mnemonic is pressed 102 * @param released 103 * the name of the action in the action map to invoke when the 104 * mnemonic is released (if the action is a toggle style, then 105 * this parameter should be {@code null}) 106 * @throws NullPointerException 107 * if the component is {@code null} 108 */ 109 public static void updateMnemonicBinding(JComponent c, String pressed, String released) { 110 Class<?> clazz = c.getClass(); 111 int m = -1; 112 113 try { 114 Method mtd = clazz.getMethod("getMnemonic"); 115 m = (Integer) mtd.invoke(c); 116 } catch (RuntimeException e) { 117 throw e; 118 } catch (Exception e) { 119 throw new IllegalArgumentException("unable to access mnemonic", e); 120 } 121 122 InputMap map = SwingUtilities.getUIInputMap(c, 123 JComponent.WHEN_IN_FOCUSED_WINDOW); 124 125 if (m != 0) { 126 if (map == null) { 127 map = new ComponentInputMapUIResource(c); 128 SwingUtilities.replaceUIInputMap(c, 129 JComponent.WHEN_IN_FOCUSED_WINDOW, map); 130 } 131 132 map.clear(); 133 134 //TODO is ALT_MASK right for all platforms? 135 map.put(KeyStroke.getKeyStroke(m, InputEvent.ALT_MASK, false), 136 pressed); 137 map.put(KeyStroke.getKeyStroke(m, InputEvent.ALT_MASK, true), 138 released); 139 map.put(KeyStroke.getKeyStroke(m, 0, true), released); 140 } else { 141 if (map != null) { 142 map.clear(); 143 } 144 } 145 } 146 147 private static Component[] getChildren(Component c) { 148 Component[] children = null; 149 150 if (c instanceof MenuElement) { 151 MenuElement[] elements = ((MenuElement) c).getSubElements(); 152 children = new Component[elements.length]; 153 154 for (int i = 0; i < elements.length; i++) { 155 children[i] = elements[i].getComponent(); 156 } 157 } else if (c instanceof Container) { 158 children = ((Container) c).getComponents(); 159 } 160 161 return children; 162 } 163 164 /** 165 * Enables or disables of the components in the tree starting with {@code c}. 166 * 167 * @param c 168 * the starting component 169 * @param enabled 170 * {@code true} if the component is to enabled; {@code false} otherwise 171 */ 172 public static void setComponentTreeEnabled(Component c, boolean enabled) { 173 c.setEnabled(enabled); 174 175 Component[] children = getChildren(c); 176 177 if (children != null) { 178 for(int i = 0; i < children.length; i++) { 179 setComponentTreeEnabled(children[i], enabled); 180 } 181 } 182 } 183 184 /** 185 * Sets the locale for an entire component hierarchy to the specified 186 * locale. 187 * 188 * @param c 189 * the starting component 190 * @param locale 191 * the locale to set 192 */ 193 public static void setComponentTreeLocale(Component c, Locale locale) { 194 c.setLocale(locale); 195 196 Component[] children = getChildren(c); 197 198 if (children != null) { 199 for(int i = 0; i < children.length; i++) { 200 setComponentTreeLocale(children[i], locale); 201 } 202 } 203 } 204 205 /** 206 * Sets the background for an entire component hierarchy to the specified 207 * color. 208 * 209 * @param c 210 * the starting component 211 * @param color 212 * the color to set 213 */ 214 public static void setComponentTreeBackground(Component c, Color color) { 215 c.setBackground(color); 216 217 Component[] children = getChildren(c); 218 219 if (children != null) { 220 for(int i = 0; i < children.length; i++) { 221 setComponentTreeBackground(children[i], color); 222 } 223 } 224 } 225 226 /** 227 * Sets the foreground for an entire component hierarchy to the specified 228 * color. 229 * 230 * @param c 231 * the starting component 232 * @param color 233 * the color to set 234 */ 235 public static void setComponentTreeForeground(Component c, Color color) { 236 c.setForeground(color); 237 238 Component[] children = getChildren(c); 239 240 if (children != null) { 241 for(int i = 0; i < children.length; i++) { 242 setComponentTreeForeground(children[i], color); 243 } 244 } 245 } 246 247 /** 248 * Sets the font for an entire component hierarchy to the specified font. 249 * 250 * @param c 251 * the starting component 252 * @param font 253 * the font to set 254 */ 255 public static void setComponentTreeFont(Component c, Font font) { 256 c.setFont(font); 257 258 Component[] children = getChildren(c); 259 260 if (children != null) { 261 for(int i = 0; i < children.length; i++) { 262 setComponentTreeFont(children[i], font); 263 } 264 } 265 } 266 267 private static String STYLESHEET = 268 "body { margin-top: 0; margin-bottom: 0; margin-left: 0; margin-right: 0;" 269 + " font-family: %s; font-size: %dpt; }" 270 + "a, p, li { margin-top: 0; margin-bottom: 0; margin-left: 0;" 271 + " margin-right: 0; font-family: %s; font-size: %dpt; }"; 272 273 /** 274 * Sets the font used for HTML displays to the specified font. Components 275 * that display HTML do not necessarily honor font properties, since the 276 * HTML document can override these values. Calling {@code setHtmlFont} 277 * after the data is set will force the HTML display to use the font 278 * specified to this method. 279 * 280 * @param doc 281 * the HTML document to update 282 * @param font 283 * the font to use 284 * @throws NullPointerException 285 * if any parameter is {@code null} 286 */ 287 public static void setHtmlFont(HTMLDocument doc, Font font) { 288 String stylesheet = String.format(STYLESHEET, font.getName(), 289 font.getSize(), font.getName(), font.getSize()); 290 291 try { 292 doc.getStyleSheet().loadRules(new StringReader(stylesheet), null); 293 } catch (IOException e) { 294 //this should never happen with our sheet 295 throw new IllegalStateException(e); 296 } 297 } 298 299 /** 300 * Updates the componentTreeUI of all top-level windows of the 301 * current application. 302 * 303 */ 304 public static void updateAllComponentTreeUIs() { 305 for (Frame frame : Frame.getFrames()) { 306 updateAllComponentTreeUIs(frame); 307 } 308 309 } 310 311 312 313 /** 314 * Updates the componentTreeUI of the given window and all its 315 * owned windows, recursively. 316 * 317 * 318 * @param window the window to update 319 */ 320 public static void updateAllComponentTreeUIs(Window window) { 321 SwingUtilities.updateComponentTreeUI(window); 322 for (Window owned : window.getOwnedWindows()) { 323 updateAllComponentTreeUIs(owned); 324 } 325 } 326 327 /** 328 * An improved version of 329 * {@link SwingUtilities#getAncestorOfClass(Class, Component)}. This method 330 * traverses {@code JPopupMenu} invoker and uses generics to return an 331 * appropriately typed object. 332 * 333 * @param <T> 334 * the type of ancestor to find 335 * @param clazz 336 * the class instance of the ancestor to find 337 * @param c 338 * the component to start the search from 339 * @return an ancestor of the correct type or {@code null} if no such 340 * ancestor exists. This method also returns {@code null} if any 341 * parameter is {@code null}. 342 */ 343 @SuppressWarnings("unchecked") 344 public static <T> T getAncestor(Class<T> clazz, Component c) { 345 if (clazz == null || c == null) { 346 return null; 347 } 348 349 Component parent = c.getParent(); 350 351 while (parent != null && !(clazz.isInstance(parent))) { 352 parent = parent instanceof JPopupMenu 353 ? ((JPopupMenu) parent).getInvoker() : parent.getParent(); 354 } 355 356 return (T) parent; 357 } 358 359 /** 360 * Returns whether the component is part of the parent's 361 * container hierarchy. If a parent in the chain is of type 362 * JPopupMenu, the parent chain of its invoker is walked. 363 * 364 * @param focusOwner 365 * @param parent 366 * @return true if the component is contained under the parent's 367 * hierarchy, coping with JPopupMenus. 368 */ 369 public static boolean isDescendingFrom(Component focusOwner, Component parent) { 370 while (focusOwner != null) { 371 if (focusOwner instanceof JPopupMenu) { 372 focusOwner = ((JPopupMenu) focusOwner).getInvoker(); 373 if (focusOwner == null) { 374 return false; 375 } 376 } 377 if (focusOwner == parent) { 378 return true; 379 } 380 focusOwner = focusOwner.getParent(); 381 } 382 return false; 383 } 384 385 /** 386 * Obtains a {@code TranslucentRepaintManager} from the specified manager. 387 * If the current manager is a {@code TranslucentRepaintManager} or a 388 * {@code ForwardingRepaintManager} that contains a {@code 389 * TranslucentRepaintManager}, then the passed in manager is returned. 390 * Otherwise a new repaint manager is created and returned. 391 * 392 * @param delegate 393 * the current repaint manager 394 * @return a non-{@code null} {@code TranslucentRepaintManager} 395 * @throws NullPointerException if {@code delegate} is {@code null} 396 */ 397 static RepaintManager getTranslucentRepaintManager(RepaintManager delegate) { 398 RepaintManager manager = delegate; 399 400 while (manager != null && !manager.getClass().isAnnotationPresent(TranslucentRepaintManager.class)) { 401 if (manager instanceof ForwardingRepaintManager) { 402 manager = ((ForwardingRepaintManager) manager).getDelegateManager(); 403 } else { 404 manager = null; 405 } 406 } 407 408 return manager == null ? new RepaintManagerX(delegate) : delegate; 409 } 410 411 /** 412 * Checks and returns whether the given property should be replaced 413 * by the UI's default value. 414 * 415 * @param property the property to check. 416 * @return true if the given property should be replaced by the UI's 417 * default value, false otherwise. 418 */ 419 public static boolean isUIInstallable(Object property) { 420 return (property == null) || (property instanceof UIResource); 421 } 422 423 }