/* * $Id: SwingXUtilities.java,v 1.11 2009/04/02 20:35:54 kschaefe Exp $ * * Copyright 2008 Sun Microsystems, Inc., 4150 Network Circle, * Santa Clara, California 95054, U.S.A. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jdesktop.swingx; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Font; import java.awt.Frame; import java.awt.Window; import java.awt.event.InputEvent; import java.io.IOException; import java.io.StringReader; import java.lang.reflect.Method; import java.util.Locale; import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import javax.swing.MenuElement; import javax.swing.RepaintManager; import javax.swing.SwingUtilities; import javax.swing.plaf.ComponentInputMapUIResource; import javax.swing.plaf.UIResource; import javax.swing.text.html.HTMLDocument; /** * A collection of utility methods for Swing(X) classes. * * <ul> * PENDING JW: think about location of this class and/or its methods, Options: * * <li> move this class to the swingx utils package which already has a bunch of xxUtils * <li> move methods between xxUtils classes as appropriate (one window/comp related util) * <li> keep here in swingx (consistent with swingutilities in core) * </ul> * @author Karl George Schaefer */ public final class SwingXUtilities { private SwingXUtilities() { //does nothing } /** * A helper for creating and updating key bindings for components with * mnemonics. The {@code pressed} action will be invoked when the mnemonic * is activated. * <p> * TODO establish an interface for the mnemonic properties, such as {@code * MnemonicEnabled} and change signature to {@code public static <T extends * JComponent & MnemonicEnabled> void updateMnemonicBinding(T c, String * pressed)} * * @param c * the component bindings to update * @param pressed * the name of the action in the action map to invoke when the * mnemonic is pressed * @throws NullPointerException * if the component is {@code null} */ public static void updateMnemonicBinding(JComponent c, String pressed) { updateMnemonicBinding(c, pressed, null); } /** * A helper for creating and updating key bindings for components with * mnemonics. The {@code pressed} action will be invoked when the mnemonic * is activated and the {@code released} action will be invoked when the * mnemonic is deactivated. * <p> * TODO establish an interface for the mnemonic properties, such as {@code * MnemonicEnabled} and change signature to {@code public static <T extends * JComponent & MnemonicEnabled> void updateMnemonicBinding(T c, String * pressed, String released)} * * @param c * the component bindings to update * @param pressed * the name of the action in the action map to invoke when the * mnemonic is pressed * @param released * the name of the action in the action map to invoke when the * mnemonic is released (if the action is a toggle style, then * this parameter should be {@code null}) * @throws NullPointerException * if the component is {@code null} */ public static void updateMnemonicBinding(JComponent c, String pressed, String released) { Class<?> clazz = c.getClass(); int m = -1; try { Method mtd = clazz.getMethod("getMnemonic"); m = (Integer) mtd.invoke(c); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new IllegalArgumentException("unable to access mnemonic", e); } InputMap map = SwingUtilities.getUIInputMap(c, JComponent.WHEN_IN_FOCUSED_WINDOW); if (m != 0) { if (map == null) { map = new ComponentInputMapUIResource(c); SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_IN_FOCUSED_WINDOW, map); } map.clear(); //TODO is ALT_MASK right for all platforms? map.put(KeyStroke.getKeyStroke(m, InputEvent.ALT_MASK, false), pressed); map.put(KeyStroke.getKeyStroke(m, InputEvent.ALT_MASK, true), released); map.put(KeyStroke.getKeyStroke(m, 0, true), released); } else { if (map != null) { map.clear(); } } } private static Component[] getChildren(Component c) { Component[] children = null; if (c instanceof MenuElement) { MenuElement[] elements = ((MenuElement) c).getSubElements(); children = new Component[elements.length]; for (int i = 0; i < elements.length; i++) { children[i] = elements[i].getComponent(); } } else if (c instanceof Container) { children = ((Container) c).getComponents(); } return children; } /** * Enables or disables of the components in the tree starting with {@code c}. * * @param c * the starting component * @param enabled * {@code true} if the component is to enabled; {@code false} otherwise */ public static void setComponentTreeEnabled(Component c, boolean enabled) { c.setEnabled(enabled); Component[] children = getChildren(c); if (children != null) { for(int i = 0; i < children.length; i++) { setComponentTreeEnabled(children[i], enabled); } } } /** * Sets the locale for an entire component hierarchy to the specified * locale. * * @param c * the starting component * @param locale * the locale to set */ public static void setComponentTreeLocale(Component c, Locale locale) { c.setLocale(locale); Component[] children = getChildren(c); if (children != null) { for(int i = 0; i < children.length; i++) { setComponentTreeLocale(children[i], locale); } } } /** * Sets the background for an entire component hierarchy to the specified * color. * * @param c * the starting component * @param color * the color to set */ public static void setComponentTreeBackground(Component c, Color color) { c.setBackground(color); Component[] children = getChildren(c); if (children != null) { for(int i = 0; i < children.length; i++) { setComponentTreeBackground(children[i], color); } } } /** * Sets the foreground for an entire component hierarchy to the specified * color. * * @param c * the starting component * @param color * the color to set */ public static void setComponentTreeForeground(Component c, Color color) { c.setForeground(color); Component[] children = getChildren(c); if (children != null) { for(int i = 0; i < children.length; i++) { setComponentTreeForeground(children[i], color); } } } /** * Sets the font for an entire component hierarchy to the specified font. * * @param c * the starting component * @param font * the font to set */ public static void setComponentTreeFont(Component c, Font font) { c.setFont(font); Component[] children = getChildren(c); if (children != null) { for(int i = 0; i < children.length; i++) { setComponentTreeFont(children[i], font); } } } private static String STYLESHEET = "body { margin-top: 0; margin-bottom: 0; margin-left: 0; margin-right: 0;" + " font-family: %s; font-size: %dpt; }" + "a, p, li { margin-top: 0; margin-bottom: 0; margin-left: 0;" + " margin-right: 0; font-family: %s; font-size: %dpt; }"; /** * Sets the font used for HTML displays to the specified font. Components * that display HTML do not necessarily honor font properties, since the * HTML document can override these values. Calling {@code setHtmlFont} * after the data is set will force the HTML display to use the font * specified to this method. * * @param doc * the HTML document to update * @param font * the font to use * @throws NullPointerException * if any parameter is {@code null} */ public static void setHtmlFont(HTMLDocument doc, Font font) { String stylesheet = String.format(STYLESHEET, font.getName(), font.getSize(), font.getName(), font.getSize()); try { doc.getStyleSheet().loadRules(new StringReader(stylesheet), null); } catch (IOException e) { //this should never happen with our sheet throw new IllegalStateException(e); } } /** * Updates the componentTreeUI of all top-level windows of the * current application. * */ public static void updateAllComponentTreeUIs() { for (Frame frame : Frame.getFrames()) { updateAllComponentTreeUIs(frame); } } /** * Updates the componentTreeUI of the given window and all its * owned windows, recursively. * * * @param window the window to update */ public static void updateAllComponentTreeUIs(Window window) { SwingUtilities.updateComponentTreeUI(window); for (Window owned : window.getOwnedWindows()) { updateAllComponentTreeUIs(owned); } } /** * An improved version of * {@link SwingUtilities#getAncestorOfClass(Class, Component)}. This method * traverses {@code JPopupMenu} invoker and uses generics to return an * appropriately typed object. * * @param <T> * the type of ancestor to find * @param clazz * the class instance of the ancestor to find * @param c * the component to start the search from * @return an ancestor of the correct type or {@code null} if no such * ancestor exists. This method also returns {@code null} if any * parameter is {@code null}. */ @SuppressWarnings("unchecked") public static <T> T getAncestor(Class<T> clazz, Component c) { if (clazz == null || c == null) { return null; } Component parent = c.getParent(); while (parent != null && !(clazz.isInstance(parent))) { parent = c instanceof JPopupMenu ? ((JPopupMenu) c).getInvoker() : c.getParent(); } return (T) parent; } /** * Returns whether the component is part of the parent's * container hierarchy. If a parent in the chain is of type * JPopupMenu, the parent chain of its invoker is walked. * * @param focusOwner * @param parent * @return true if the component is contained under the parent's * hierarchy, coping with JPopupMenus. */ public static boolean isDescendingFrom(Component focusOwner, Component parent) { while (focusOwner != null) { if (focusOwner instanceof JPopupMenu) { focusOwner = ((JPopupMenu) focusOwner).getInvoker(); if (focusOwner == null) { return false; } } if (focusOwner == parent) { return true; } focusOwner = focusOwner.getParent(); } return false; } /** * Obtains a {@code TranslucentRepaintManager} from the specified manager. * If the current manager is a {@code TranslucentRepaintManager} or a * {@code ForwardingRepaintManager} that contains a {@code * TranslucentRepaintManager}, then the passed in manager is returned. * Otherwise a new repaint manager is created and returned. * * @param delegate * the current repaint manager * @return a non-{@code null} {@code TranslucentRepaintManager} * @throws NullPointerException if {@code delegate} is {@code null} */ static RepaintManager getTranslucentRepaintManager(RepaintManager delegate) { RepaintManager manager = delegate; while (manager != null && !manager.getClass().isAnnotationPresent(TranslucentRepaintManager.class)) { if (manager instanceof ForwardingRepaintManager) { manager = ((ForwardingRepaintManager) manager).getDelegateManager(); } else { manager = null; } } return manager == null ? new RepaintManagerX(delegate) : delegate; } /** * Checks and returns whether the given property should be replaced * by the UI's default value. * * @param property the property to check. * @return true if the given property should be replaced by the UI's * default value, false otherwise. */ public static boolean isUIInstallable(Object property) { return (property == null) || (property instanceof UIResource); } }