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 }