/*
* SwingTools.java 21 oct. 2008
*
* Sweet Home 3D, Copyright (c) 2008 Emmanuel PUYBARET / eTeks <info@eteks.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.eteks.sweethome3d.swing;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.awt.image.RGBImageFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.concurrent.Executors;
import javax.imageio.ImageIO;
import javax.jnlp.BasicService;
import javax.jnlp.ServiceManager;
import javax.jnlp.UnavailableServiceException;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JToggleButton;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.text.JTextComponent;
import com.eteks.sweethome3d.model.TextureImage;
import com.eteks.sweethome3d.model.UserPreferences;
import com.eteks.sweethome3d.tools.OperatingSystem;
/**
* Gathers some useful tools for Swing.
* @author Emmanuel Puybaret
*/
public class SwingTools {
// Borders for focused views
private static Border unfocusedViewBorder;
private static Border focusedViewBorder;
private SwingTools() {
// This class contains only tools
}
/**
* Updates the border of <code>component</code> with an empty border
* changed to a colored border when it will gain focus.
* If the <code>component</code> component is the child of a <code>JViewPort</code>
* instance this border will be installed on its scroll pane parent.
*/
public static void installFocusBorder(JComponent component) {
if (unfocusedViewBorder == null) {
Border unfocusedViewInteriorBorder = new AbstractBorder() {
private Color topLeftColor;
private Color botomRightColor;
private Insets insets = new Insets(1, 1, 1, 1);
{
if (OperatingSystem.isMacOSX()) {
this.topLeftColor = Color.GRAY;
this.botomRightColor = Color.LIGHT_GRAY;
} else {
this.topLeftColor = UIManager.getColor("TextField.darkShadow");
this.botomRightColor = UIManager.getColor("TextField.shadow");
}
}
public Insets getBorderInsets(Component c) {
return this.insets;
}
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
Color previousColor = g.getColor();
Rectangle rect = getInteriorRectangle(c, x, y, width, height);
g.setColor(topLeftColor);
g.drawLine(rect.x - 1, rect.y - 1, rect.x + rect.width, rect.y - 1);
g.drawLine(rect.x - 1, rect.y - 1, rect.x - 1, rect.y + rect.height);
g.setColor(botomRightColor);
g.drawLine(rect.x, rect.y + rect.height, rect.x + rect.width, rect.y + rect.height);
g.drawLine(rect.x + rect.width, rect.y, rect.x + rect.width, rect.y + rect.height);
g.setColor(previousColor);
}
};
if (OperatingSystem.isMacOSXLeopardOrSuperior()) {
unfocusedViewBorder = BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(UIManager.getColor("Panel.background"), 2),
unfocusedViewInteriorBorder);
focusedViewBorder = new AbstractBorder() {
private Insets insets = new Insets(3, 3, 3, 3);
public Insets getBorderInsets(Component c) {
return this.insets;
}
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
Color previousColor = g.getColor();
// Paint a gradient paint around component
Rectangle rect = getInteriorRectangle(c, x, y, width, height);
g.setColor(Color.GRAY);
g.drawLine(rect.x - 1, rect.y - 1, rect.x + rect.width, rect.y - 1);
g.drawLine(rect.x - 1, rect.y - 1, rect.x - 1, rect.y + rect.height);
g.setColor(Color.LIGHT_GRAY);
g.drawLine(rect.x, rect.y + rect.height, rect.x + rect.width, rect.y + rect.height);
g.drawLine(rect.x + rect.width, rect.y, rect.x + rect.width, rect.y + rect.height);
Color focusColor = UIManager.getColor("Focus.color");
int transparencyOutline = 128;
int transparencyInline = 180;
if (focusColor == null) {
focusColor = UIManager.getColor("textHighlight");
transparencyOutline = 128;
transparencyInline = 255;
}
g.setColor(new Color(focusColor.getRed(), focusColor.getGreen(), focusColor.getBlue(), transparencyOutline));
g.drawRoundRect(rect.x - 3, rect.y - 3, rect.width + 5, rect.height + 5, 6, 6);
g.drawRect(rect.x - 1, rect.y - 1, rect.width + 1, rect.height + 1);
g.setColor(new Color(focusColor.getRed(), focusColor.getGreen(), focusColor.getBlue(), transparencyInline));
g.drawRoundRect(rect.x - 2, rect.y - 2, rect.width + 3, rect.height + 3, 4, 4);
// Draw corners
g.setColor(UIManager.getColor("Panel.background"));
g.drawLine(rect.x - 3, rect.y - 3, rect.x - 2, rect.y - 3);
g.drawLine(rect.x - 3, rect.y - 2, rect.x - 3, rect.y - 2);
g.drawLine(rect.x + rect.width + 1, rect.y - 3, rect.x + rect.width + 2, rect.y - 3);
g.drawLine(rect.x + rect.width + 2, rect.y - 2, rect.x + rect.width + 2, rect.y - 2);
g.drawLine(rect.x - 3, rect.y + rect.height + 2, rect.x - 2, rect.y + rect.height + 2);
g.drawLine(rect.x - 3, rect.y + rect.height + 1, rect.x - 3, rect.y + rect.height + 1);
g.drawLine(rect.x + rect.width + 1, rect.y + rect.height + 2, rect.x + rect.width + 2, rect.y + rect.height + 2);
g.drawLine(rect.x + rect.width + 2, rect.y + rect.height + 1, rect.x + rect.width + 2, rect.y + rect.height + 1);
g.setColor(previousColor);
}
};
} else {
if (OperatingSystem.isMacOSX()) {
unfocusedViewBorder = BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(UIManager.getColor("Panel.background"), 1),
unfocusedViewInteriorBorder);
} else {
unfocusedViewBorder = BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(1, 1, 1, 1),
unfocusedViewInteriorBorder);
}
focusedViewBorder = BorderFactory.createLineBorder(UIManager.getColor("textHighlight"), 2);
}
}
final JComponent feedbackComponent;
if (component.getParent() instanceof JViewport
&& component.getParent().getParent() instanceof JScrollPane) {
feedbackComponent = (JComponent)component.getParent().getParent();
} else {
feedbackComponent = component;
}
feedbackComponent.setBorder(unfocusedViewBorder);
component.addFocusListener(new FocusListener() {
public void focusLost(FocusEvent ev) {
if (feedbackComponent.getBorder() == focusedViewBorder) {
feedbackComponent.setBorder(unfocusedViewBorder);
}
}
public void focusGained(FocusEvent ev) {
if (feedbackComponent.getBorder() == unfocusedViewBorder) {
feedbackComponent.setBorder(focusedViewBorder);
}
}
});
}
/**
* Updates the Swing resource bundles in use from the current Locale and class loader.
*/
public static void updateSwingResourceLanguage() {
updateSwingResourceLanguage(Arrays.asList(new ClassLoader [] {SwingTools.class.getClassLoader()}));
}
/**
* Updates the Swing resource bundles in use from the current Locale and the class loaders of preferences.
*/
public static void updateSwingResourceLanguage(UserPreferences preferences) {
updateSwingResourceLanguage(preferences.getResourceClassLoaders());
}
/**
* Updates the Swing resource bundles in use from the current Locale and class loaders.
*/
private static void updateSwingResourceLanguage(List<ClassLoader> classLoaders) {
// Read Swing localized properties because Swing doesn't update its internal strings automatically
// when default Locale is updated (see bug http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4884480)
updateSwingResourceBundle("com.sun.swing.internal.plaf.metal.resources.metal", classLoaders);
updateSwingResourceBundle("com.sun.swing.internal.plaf.basic.resources.basic", classLoaders);
if (UIManager.getLookAndFeel().getClass().getName().equals("com.sun.java.swing.plaf.gtk.GTKLookAndFeel")) {
updateSwingResourceBundle("com.sun.java.swing.plaf.gtk.resources.gtk", classLoaders);
} else if (UIManager.getLookAndFeel().getClass().getName().equals("com.sun.java.swing.plaf.motif.MotifLookAndFeel")) {
updateSwingResourceBundle("com.sun.java.swing.plaf.motif.resources.motif", classLoaders);
}
}
/**
* Updates a Swing resource bundle in use from the current Locale.
*/
private static void updateSwingResourceBundle(String swingResource, List<ClassLoader> classLoaders) {
ResourceBundle resource = ResourceBundle.getBundle(swingResource, Locale.ENGLISH);
try {
Locale defaultLocale = Locale.getDefault();
for (ClassLoader classLoader : classLoaders) {
ResourceBundle bundle = ResourceBundle.getBundle(swingResource, defaultLocale, classLoader);
if (defaultLocale.equals(bundle.getLocale())) {
resource = bundle;
break;
} else if (!resource.getLocale().getLanguage().equals(bundle.getLocale().getLanguage())
&& defaultLocale.getLanguage().equals(bundle.getLocale().getLanguage())) {
resource = bundle;
// Don't break in case a bundle with language + country is found with an other class loader
}
}
} catch (MissingResourceException ex) {
}
// Update UIManager properties
for (Enumeration<?> it = resource.getKeys(); it.hasMoreElements(); ) {
String property = (String)it.nextElement();
UIManager.put(property, resource.getString(property));
}
}
/**
* Returns a localized text for menus items and labels depending on the system.
*/
public static String getLocalizedLabelText(UserPreferences preferences,
Class<?> resourceClass,
String resourceKey,
Object ... resourceParameters) {
String localizedString = preferences.getLocalizedString(resourceClass, resourceKey, resourceParameters);
// Under Mac OS X, remove bracketed upper case roman letter used in oriental languages to indicate mnemonic
String language = Locale.getDefault().getLanguage();
if (OperatingSystem.isMacOSX()
&& (language.equals(Locale.CHINESE.getLanguage())
|| language.equals(Locale.JAPANESE.getLanguage())
|| language.equals(Locale.KOREAN.getLanguage()))) {
int openingBracketIndex = localizedString.indexOf('(');
if (openingBracketIndex != -1) {
int closingBracketIndex = localizedString.indexOf(')');
if (openingBracketIndex == closingBracketIndex - 2) {
char c = localizedString.charAt(openingBracketIndex + 1);
if (c >= 'A' && c <= 'Z') {
localizedString = localizedString.substring(0, openingBracketIndex)
+ localizedString.substring(closingBracketIndex + 1);
}
}
}
}
return localizedString;
}
/**
* Adds focus and mouse listeners to the given <code>textComponent</code> that will
* select all its text when it gains focus by transfer.
*/
public static void addAutoSelectionOnFocusGain(final JTextComponent textComponent) {
// A focus and mouse listener able to select text field characters
// when it gains focus after a focus transfer
class SelectionOnFocusManager extends MouseAdapter implements FocusListener {
private boolean mousePressedInTextField = false;
private int selectionStartBeforeFocusLost = -1;
private int selectionEndBeforeFocusLost = -1;
@Override
public void mousePressed(MouseEvent ev) {
this.mousePressedInTextField = true;
this.selectionStartBeforeFocusLost = -1;
}
public void focusLost(FocusEvent ev) {
if (ev.getOppositeComponent() == null
|| SwingUtilities.getWindowAncestor(ev.getOppositeComponent())
!= SwingUtilities.getWindowAncestor(textComponent)) {
// Keep selection indices when focus on text field is transfered
// to an other window
this.selectionStartBeforeFocusLost = textComponent.getSelectionStart();
this.selectionEndBeforeFocusLost = textComponent.getSelectionEnd();
} else {
this.selectionStartBeforeFocusLost = -1;
}
}
public void focusGained(FocusEvent ev) {
if (this.selectionStartBeforeFocusLost != -1) {
EventQueue.invokeLater(new Runnable() {
public void run() {
// Reselect the same characters in text field
textComponent.setSelectionStart(selectionStartBeforeFocusLost);
textComponent.setSelectionEnd(selectionEndBeforeFocusLost);
}
});
} else if (!this.mousePressedInTextField
&& ev.getOppositeComponent() != null
&& SwingUtilities.getWindowAncestor(ev.getOppositeComponent())
== SwingUtilities.getWindowAncestor(textComponent)) {
EventQueue.invokeLater(new Runnable() {
public void run() {
// Select all characters when text field got the focus because of a transfer
textComponent.selectAll();
}
});
}
this.mousePressedInTextField = false;
}
};
SelectionOnFocusManager selectionOnFocusManager = new SelectionOnFocusManager();
textComponent.addFocusListener(selectionOnFocusManager);
textComponent.addMouseListener(selectionOnFocusManager);
}
/**
* Forces radio buttons to be deselected even if they belong to a button group.
*/
public static void deselectAllRadioButtons(JRadioButton ... radioButtons) {
for (JRadioButton radioButton : radioButtons) {
if (radioButton != null) {
ButtonGroup group = ((JToggleButton.ToggleButtonModel)radioButton.getModel()).getGroup();
group.remove(radioButton);
radioButton.setSelected(false);
group.add(radioButton);
}
}
}
/**
* Displays <code>messageComponent</code> in a modal dialog box, giving focus to one of its components.
*/
public static int showConfirmDialog(JComponent parentComponent,
JComponent messageComponent,
String title,
final JComponent focusedComponent) {
JOptionPane optionPane = new JOptionPane(messageComponent,
JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION);
parentComponent = SwingUtilities.getRootPane(parentComponent);
if (parentComponent != null) {
optionPane.setComponentOrientation(parentComponent.getComponentOrientation());
}
final JDialog dialog = optionPane.createDialog(parentComponent, title);
if (focusedComponent != null) {
// Add a listener that transfer focus to focusedComponent when dialog is shown
dialog.addComponentListener(new ComponentAdapter() {
@Override
public void componentShown(ComponentEvent ev) {
requestFocus(focusedComponent);
dialog.removeComponentListener(this);
}
});
}
dialog.setVisible(true);
dialog.dispose();
Object value = optionPane.getValue();
if (value instanceof Integer) {
return (Integer)value;
} else {
return JOptionPane.CLOSED_OPTION;
}
}
/**
* Requests the focus for the given component.
*/
private static void requestFocus(final JComponent focusedComponent) {
if (!focusedComponent.requestFocusInWindow()) {
// Prefer to call requestFocusInWindow in a timer with a small delay
// than calling it with EnventQueue#invokeLater to ensure it always works
new Timer(50, new ActionListener() {
public void actionPerformed(ActionEvent ev) {
focusedComponent.requestFocusInWindow();
((Timer)ev.getSource()).stop();
}
}).start();
}
}
/**
* Displays <code>messageComponent</code> in a modal dialog box, giving focus to one of its components.
*/
public static void showMessageDialog(JComponent parentComponent,
JComponent messageComponent,
String title,
int messageType,
final JComponent focusedComponent) {
JOptionPane optionPane = new JOptionPane(messageComponent, messageType, JOptionPane.DEFAULT_OPTION);
parentComponent = SwingUtilities.getRootPane(parentComponent);
if (parentComponent != null) {
optionPane.setComponentOrientation(parentComponent.getComponentOrientation());
}
final JDialog dialog = optionPane.createDialog(parentComponent, title);
if (focusedComponent != null) {
// Add a listener that transfer focus to focusedComponent when dialog is shown
dialog.addComponentListener(new ComponentAdapter() {
@Override
public void componentShown(ComponentEvent ev) {
requestFocus(focusedComponent);
dialog.removeComponentListener(this);
}
});
}
dialog.setVisible(true);
dialog.dispose();
}
private static Map<TextureImage, BufferedImage> patternImages;
/**
* Returns the image matching a given pattern.
*/
public static BufferedImage getPatternImage(TextureImage pattern,
Color backgroundColor,
Color foregroundColor) {
if (patternImages == null) {
patternImages = new HashMap<TextureImage, BufferedImage>();
}
BufferedImage image = new BufferedImage(
(int)pattern.getWidth(), (int)pattern.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D imageGraphics = (Graphics2D)image.getGraphics();
imageGraphics.setColor(backgroundColor);
imageGraphics.fillRect(0, 0, image.getWidth(), image.getHeight());
// Get pattern image from cache
BufferedImage patternImage = patternImages.get(pattern);
if (patternImage == null) {
try {
InputStream imageInput = pattern.getImage().openStream();
patternImage = ImageIO.read(imageInput);
imageInput.close();
patternImages.put(pattern, patternImage);
} catch (IOException ex) {
throw new IllegalArgumentException("Can't read pattern image " + pattern.getName());
}
}
// Draw the pattern image with foreground color
final int foregroundColorRgb = foregroundColor.getRGB() & 0xFFFFFF;
imageGraphics.drawImage(Toolkit.getDefaultToolkit().createImage(
new FilteredImageSource(patternImage.getSource(),
new RGBImageFilter() {
{
this.canFilterIndexColorModel = true;
}
@Override
public int filterRGB(int x, int y, int argb) {
// Always use foreground color and alpha
return (argb & 0xFF000000) | foregroundColorRgb;
}
})), 0, 0, null);
imageGraphics.dispose();
return image;
}
/**
* Returns the border of a component where a user may drop objects.
*/
public static Border getDropableComponentBorder() {
Border border = null;
if (OperatingSystem.isMacOSXLeopardOrSuperior()) {
border = UIManager.getBorder("InsetBorder.aquaVariant");
}
if (border == null) {
border = BorderFactory.createLoweredBevelBorder();
}
return border;
}
/**
* Displays the image referenced by <code>imageUrl</code> in an AWT window
* disposed once an other AWT frame is created.
* If the <code>imageUrl</code> is incorrect, nothing happens.
*/
public static void showSplashScreenWindow(URL imageUrl) {
try {
final BufferedImage image = ImageIO.read(imageUrl);
final Window splashScreenWindow = new Window(new Frame()) {
@Override
public void paint(Graphics g) {
g.drawImage(image, 0, 0, this);
}
};
splashScreenWindow.setSize(image.getWidth(), image.getHeight());
splashScreenWindow.setLocationRelativeTo(null);
splashScreenWindow.setVisible(true);
Executors.newSingleThreadExecutor().execute(new Runnable() {
public void run() {
try {
Thread.sleep(500);
while (splashScreenWindow.isVisible()) {
// If an other frame is showing, dispose splash window
EventQueue.invokeLater(new Runnable() {
public void run() {
for (Frame frame : Frame.getFrames()) {
if (frame.isShowing()) {
splashScreenWindow.dispose();
}
}
}
});
Thread.sleep(300);
}
} catch (InterruptedException ex) {
EventQueue.invokeLater(new Runnable() {
public void run() {
splashScreenWindow.dispose();
}
});
};
}
});
} catch (IOException ex) {
// Ignore splash screen
}
}
/**
* Returns a new panel with a border and the given <code>title</code>
*/
public static JPanel createTitledPanel(String title) {
JPanel titledPanel = new JPanel(new GridBagLayout());
Border panelBorder = BorderFactory.createTitledBorder(title);
// For systems different from Mac OS X 10.5, add an empty border
if (!OperatingSystem.isMacOSXLeopardOrSuperior()) {
panelBorder = BorderFactory.createCompoundBorder(
panelBorder, BorderFactory.createEmptyBorder(0, 2, 2, 2));
} else if (OperatingSystem.isJavaVersionGreaterOrEqual("1.7")) {
// Enlarge space at the top of the border
panelBorder = BorderFactory.createCompoundBorder(
panelBorder, BorderFactory.createEmptyBorder(10, 0, 0, 0));
}
titledPanel.setBorder(panelBorder);
return titledPanel;
}
/**
* Returns a scroll pane containing the given <code>component</code>
* that always displays scroll bars under Mac OS X.
*/
public static JScrollPane createScrollPane(JComponent component) {
JScrollPane scrollPane = new JScrollPane(component);
if (OperatingSystem.isMacOSX()) {
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
}
installFocusBorder(component);
scrollPane.setMinimumSize(new Dimension());
return scrollPane;
}
/**
* Adds a listener that will update the given popup menu to hide disabled menu items.
*/
public static void hideDisabledMenuItems(JPopupMenu popupMenu) {
popupMenu.addPopupMenuListener(new MenuItemsVisibilityListener());
}
/**
* A popup menu listener that displays only enabled menu items.
*/
private static class MenuItemsVisibilityListener implements PopupMenuListener {
public void popupMenuWillBecomeVisible(PopupMenuEvent ev) {
JPopupMenu popupMenu = (JPopupMenu)ev.getSource();
hideDisabledMenuItems(popupMenu);
// Ensure at least one item is visible
boolean allItemsInvisible = true;
for (int i = 0; i < popupMenu.getComponentCount(); i++) {
if (popupMenu.getComponent(i).isVisible()) {
allItemsInvisible = false;
break;
}
}
if (allItemsInvisible) {
popupMenu.getComponent(0).setVisible(true);
}
}
/**
* Makes useless menu items invisible.
*/
private void hideDisabledMenuItems(JPopupMenu popupMenu) {
for (int i = 0; i < popupMenu.getComponentCount(); i++) {
Component component = popupMenu.getComponent(i);
if (component instanceof JMenu) {
boolean containsEnabledItems = containsEnabledItems((JMenu)component);
component.setVisible(containsEnabledItems);
if (containsEnabledItems) {
hideDisabledMenuItems(((JMenu)component).getPopupMenu());
}
} else if (component instanceof JMenuItem) {
component.setVisible(component.isEnabled());
}
}
hideUselessSeparators(popupMenu);
}
/**
* Makes useless separators invisible.
*/
private void hideUselessSeparators(JPopupMenu popupMenu) {
boolean allMenuItemsInvisible = true;
int lastVisibleSeparatorIndex = -1;
for (int i = 0; i < popupMenu.getComponentCount(); i++) {
Component component = popupMenu.getComponent(i);
if (allMenuItemsInvisible && (component instanceof JMenuItem)) {
if (component.isVisible()) {
allMenuItemsInvisible = false;
}
} else if (component instanceof JSeparator) {
component.setVisible(!allMenuItemsInvisible);
if (!allMenuItemsInvisible) {
lastVisibleSeparatorIndex = i;
}
allMenuItemsInvisible = true;
}
}
if (lastVisibleSeparatorIndex != -1 && allMenuItemsInvisible) {
// Check if last separator is the first visible component
boolean allComponentsBeforeLastVisibleSeparatorInvisible = true;
for (int i = lastVisibleSeparatorIndex - 1; i >= 0; i--) {
if (popupMenu.getComponent(i).isVisible()) {
allComponentsBeforeLastVisibleSeparatorInvisible = false;
break;
}
}
boolean allComponentsAfterLastVisibleSeparatorInvisible = true;
for (int i = lastVisibleSeparatorIndex; i < popupMenu.getComponentCount(); i++) {
if (popupMenu.getComponent(i).isVisible()) {
allComponentsBeforeLastVisibleSeparatorInvisible = false;
break;
}
}
popupMenu.getComponent(lastVisibleSeparatorIndex).setVisible(
!allComponentsBeforeLastVisibleSeparatorInvisible && !allComponentsAfterLastVisibleSeparatorInvisible);
}
}
/**
* Returns <code>true</code> if the given <code>menu</code> contains
* at least one enabled menu item.
*/
private boolean containsEnabledItems(JMenu menu) {
boolean menuContainsEnabledItems = false;
for (int i = 0; i < menu.getMenuComponentCount() && !menuContainsEnabledItems; i++) {
Component component = menu.getMenuComponent(i);
if (component instanceof JMenu) {
menuContainsEnabledItems = containsEnabledItems((JMenu)component);
} else if (component instanceof JMenuItem) {
menuContainsEnabledItems = component.isEnabled();
}
}
return menuContainsEnabledItems;
}
public void popupMenuCanceled(PopupMenuEvent ev) {
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent ev) {
}
}
/**
* Attempts to display the given <code>url</code> in a browser and returns <code>true</code>
* if it was done successfully.
*/
public static boolean showDocumentInBrowser(URL url) {
return BrowserSupport.showDocumentInBrowser(url);
}
/**
* Separated static class to be able to exclude JNLP library from classpath.
*/
private static class BrowserSupport {
public static boolean showDocumentInBrowser(URL url) {
try {
// Lookup the javax.jnlp.BasicService object
BasicService basicService = (BasicService)ServiceManager.lookup("javax.jnlp.BasicService");
// Ignore the basic service, if it doesn't support web browser
if (basicService.isWebBrowserSupported()) {
return basicService.showDocument(url);
}
} catch (UnavailableServiceException ex) {
// Too bad : service is unavailable
} catch (LinkageError ex) {
// JNLP classes not available in classpath
System.err.println("Can't show document in browser. JNLP classes not available in classpath.");
}
return false;
}
}
/**
* Returns the children of a component of the given class.
*/
public static <T extends Component> List<T> findChildren(JComponent parent, Class<T> childrenClass) {
List<T> children = new ArrayList<T>();
findChildren(parent, childrenClass, children);
return children;
}
private static <T extends Component> void findChildren(JComponent parent, Class<T> childrenClass, List<T> children) {
for (int i = 0; i < parent.getComponentCount(); i++) {
Component child = parent.getComponent(i);
if (childrenClass.isInstance(child)) {
children.add((T)child);
} else if (child instanceof JComponent) {
findChildren((JComponent)child, childrenClass, children);
}
}
}
/**
* Returns <code>true</code> if the given rectangle is fully visible at screen.
*/
public static boolean isRectangleVisibleAtScreen(Rectangle rectangle) {
Area devicesArea = new Area();
GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
for (GraphicsDevice device : environment.getScreenDevices()) {
devicesArea.add(new Area(device.getDefaultConfiguration().getBounds()));
}
return devicesArea.contains(rectangle);
}
/**
* Returns a new custom cursor.
*/
public static Cursor createCustomCursor(URL smallCursorImageUrl,
URL largeCursorImageUrl,
float xCursorHotSpot,
float yCursorHotSpot,
String cursorName,
Cursor defaultCursor) {
if (GraphicsEnvironment.isHeadless()) {
return defaultCursor;
}
// Retrieve system cursor size
Dimension cursorSize = Toolkit.getDefaultToolkit().getBestCursorSize(16, 16);
URL cursorImageResource;
// If returned cursor size is 0, system doesn't support custom cursor
if (cursorSize.width == 0) {
return defaultCursor;
} else {
// Use a different cursor image depending on system cursor size
if (cursorSize.width > 16) {
cursorImageResource = largeCursorImageUrl;
} else {
cursorImageResource = smallCursorImageUrl;
}
try {
// Read cursor image
BufferedImage cursorImage = ImageIO.read(cursorImageResource);
// Create custom cursor from image
return Toolkit.getDefaultToolkit().createCustomCursor(cursorImage,
new Point(Math.min(cursorSize.width - 1, Math.round(cursorSize.width * xCursorHotSpot)),
Math.min(cursorSize.height - 1, Math.round(cursorSize.height * yCursorHotSpot))),
cursorName);
} catch (IOException ex) {
throw new IllegalArgumentException("Unknown resource " + cursorImageResource);
}
}
}
}