org.jdesktop.swingx.SwingXUtilities Maven / Gradle / Ivy
Show all versions of swingx-all Show documentation
/*
* $Id: SwingXUtilities.java 4262 2012-11-19 18:40:10Z kschaefe $
*
* 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 org.jdesktop.swingx.painter.Painter;
import org.jdesktop.swingx.util.OS;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.MenuElement;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentInputMapUIResource;
import javax.swing.plaf.UIResource;
import javax.swing.text.html.HTMLDocument;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.util.Locale;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* A collection of utility methods for Swing(X) classes.
*
* PENDING JW: think about location of this class and/or its methods, Options:
*
* - move this class to the swingx utils package which already has a bunch of xxUtils
*
- move methods between xxUtils classes as appropriate (one window/comp related util)
*
- keep here in swingx (consistent with swingutilities in core)
*
*
* @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.
*
* @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(T 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.
*
* @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(T c, String pressed, String released) {
int m = c.getMnemonic();
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_DOWN_MASK, false), pressed);
map.put(KeyStroke.getKeyStroke(m, InputEvent.ALT_DOWN_MASK, true), released);
map.put(KeyStroke.getKeyStroke(m, 0, true), released);
} else {
if (map != null) {
map.clear();
}
}
}
@SuppressWarnings("unchecked")
static void paintBackground(C comp, Graphics2D g) {
// we should be painting the background behind the painter if we have one
// this prevents issues with buffer reuse where visual artifacts sneak in
if (comp.isOpaque()
|| comp instanceof AlphaPaintable && ((AlphaPaintable) comp).getAlpha() < 1f
|| "Nimbus".equals(UIManager.getLookAndFeel().getID())) {
g.setColor(comp.getBackground());
g.fillRect(0, 0, comp.getWidth(), comp.getHeight());
}
Painter super C> painter = comp.getBackgroundPainter();
if (painter != null) {
if (comp.isPaintBorderInsets()) {
painter.paint(g, comp, comp.getWidth(), comp.getHeight());
} else {
Insets insets = comp.getInsets();
g.translate(insets.left, insets.top);
painter.paint(g, comp, comp.getWidth() - insets.left - insets.right, comp.getHeight() - insets.top - insets.bottom);
g.translate(-insets.left, -insets.top);
}
}
}
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 (Component child : children) {
setComponentTreeEnabled(child, 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 (Component child : children) {
setComponentTreeLocale(child, 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 (Component child : children) {
setComponentTreeBackground(child, 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 (Component child : children) {
setComponentTreeForeground(child, 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 (Component child : children) {
setComponentTreeFont(child, font);
}
}
}
private static final 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);
// }
// JW: updated to new 1.6 api - returns all windows, owned and ownerless
for (Window window : Window.getWindows()) {
SwingUtilities.updateComponentTreeUI(window);
}
}
/**
* 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);
}
}
/**
* A version of {@link SwingUtilities#invokeLater(Runnable)} that supports return values.
*
* @param the return type of the callable
* @param callable the callable to execute
* @return a future task for accessing the return value
* @see Callable
*/
public static FutureTask invokeLater(Callable callable) {
FutureTask task = new FutureTask<>(callable);
SwingUtilities.invokeLater(task);
return task;
}
/**
* A version of {@link SwingUtilities#invokeAndWait(Runnable)} that supports return values.
*
* @param the return type of the callable
* @param callable the callable to execute
* @return the value returned by the callable
* @throws InterruptedException if we're interrupted while waiting for the event dispatching thread to finish
* executing {@code callable.call()}
* @throws InvocationTargetException if an exception is thrown while running {@code callable}
* @see Callable
*/
public static T invokeAndWait(Callable callable) throws InterruptedException, InvocationTargetException {
try {
//blocks until future returns
return invokeLater(callable).get();
} catch (ExecutionException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else if (t instanceof InvocationTargetException) {
throw (InvocationTargetException) t;
} else {
throw new InvocationTargetException(t);
}
}
}
/**
* 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 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 getAncestor(Class clazz, Component c) {
if (clazz == null || c == null) {
return null;
}
Component parent = c.getParent();
while (parent != null && !clazz.isInstance(parent)) {
parent = parent instanceof JPopupMenu ? ((JPopupMenu) parent).getInvoker() : parent.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;
}
/**
* 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;
}
//---- methods c&p'ed from SwingUtilities2 to reduce dependencies on sun packages
/**
* Updates lead and anchor selection index without changing the selection.
*
* Note: this is c&p'ed from SwingUtilities2 to not have any direct
* dependency.
*
* @param selectionModel the selection model to change lead/anchor
* @param lead the lead selection index
* @param anchor the anchor selection index
*/
public static void setLeadAnchorWithoutSelection(ListSelectionModel selectionModel, int lead, int anchor) {
if (anchor == -1) {
anchor = lead;
}
if (lead == -1) {
selectionModel.setAnchorSelectionIndex(-1);
selectionModel.setLeadSelectionIndex(-1);
} else {
if (selectionModel.isSelectedIndex(lead))
selectionModel.addSelectionInterval(lead, lead);
else {
selectionModel.removeSelectionInterval(lead, lead);
}
selectionModel.setAnchorSelectionIndex(anchor);
}
}
public static boolean shouldIgnore(MouseEvent mouseEvent, JComponent component) {
return component == null || !component.isEnabled() || !SwingUtilities.isLeftMouseButton(mouseEvent) || mouseEvent.isConsumed();
}
public static int loc2IndexFileList(JList> list, Point point) {
int i = list.locationToIndex(point);
if (i != -1) {
Object localObject = list.getClientProperty("List.isFileList");
if (localObject instanceof Boolean && ((Boolean) localObject).booleanValue()
// PENDING JW: this isn't aware of sorting/filtering - fix!
&& !pointIsInActualBounds(list, i, point)) {
i = -1;
}
}
return i;
}
// PENDING JW: this isn't aware of sorting/filtering - fix!
@SuppressWarnings({"rawtypes", "unchecked"})
private static boolean pointIsInActualBounds(JList> list, int index, Point point) {
ListCellRenderer renderer = list.getCellRenderer();
ListModel> model = list.getModel();
Object element = model.getElementAt(index);
Component comp = renderer.getListCellRendererComponent(list, element, index, false, false);
Dimension prefSize = comp.getPreferredSize();
Rectangle cellBounds = list.getCellBounds(index, index);
if (!comp.getComponentOrientation().isLeftToRight()) {
cellBounds.x += cellBounds.width - prefSize.width;
}
cellBounds.width = prefSize.width;
return cellBounds.contains(point);
}
public static void adjustFocus(JComponent component) {
if (!component.hasFocus() && component.isRequestFocusEnabled())
component.requestFocus();
}
public static int convertModifiersToDropAction(int modifiers, int sourcActions) {
// PENDING JW: c'p from a decompiled SunDragSourceContextPeer
// PENDING JW: haha ... completely readable, right ;-)
int i = 0;
switch (modifiers & 0xC0) {
case 192:
i = 1073741824;
break;
case 128:
i = 1;
break;
case 64:
i = 2;
break;
default:
if ((sourcActions & 0x2) != 0) {
i = 2;
break;
}
if ((sourcActions & 0x1) != 0) {
i = 1;
break;
}
if ((sourcActions & 0x40000000) == 0)
break;
i = 1073741824;
}
// label88:
return i & sourcActions;
}
/**
* @return the accelerator for shortcuts
*/
public static KeyStroke getMenuShortCut(int key) {
int modifiers;
if (GraphicsEnvironment.isHeadless()) {
modifiers = OS.isMacOSX() ? KeyEvent.META_DOWN_MASK : KeyEvent.CTRL_DOWN_MASK;
} else {
modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
}
return KeyStroke.getKeyStroke(key, modifiers);
}
}