/*
* Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* 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 3 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, see http://www.gnu.org/licenses/
*/
package org.esa.snap.ui;
import org.esa.snap.core.param.ParamProperties;
import org.esa.snap.core.param.Parameter;
import org.esa.snap.core.util.ArrayUtils;
import org.esa.snap.core.util.Guardian;
import org.esa.snap.core.util.StringUtils;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JSpinner;
import javax.swing.KeyStroke;
import javax.swing.SpinnerNumberModel;
import javax.swing.border.Border;
import javax.swing.border.EtchedBorder;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.net.URL;
import java.text.DecimalFormat;
/**
* The <code>UIUtils</code> class provides methods frequently used in connection with graphical user interfaces.
*
* @author Norman Fomferra
* @version $Revision: 8407 $ $Date: 2010-02-14 12:58:02 +0100 (So, 14 Feb 2010) $
*/
public class UIUtils {
public static final String PROPERTY_SOURCE_PRODUCT = "SOURCE_PRODUCT";
public static final String IMAGE_RESOURCE_PATH = "/org/esa/snap/resources/images/";
public static final Color COLOR_DARK_RED = new Color(128, 0, 0);
public static final Color COLOR_DARK_BLUE = new Color(0, 0, 128);
public static final Color COLOR_DARK_GREEN = new Color(0, 128, 0);
/**
* Gets the image icon loaded from the given resource path.
* <p>Note that this method only works for images found in the classpath of the class loader which loaded this {@link UIUtils} class.
* If you are not sure, you should better use {@link #getImageURL(String, Class)}.</p>
*
* @param resourcePath the resource path
*
* @return an image icon loaded from the given resource path or <code>null</code> if it could not be found
*/
public static ImageIcon loadImageIcon(String resourcePath) {
return loadImageIcon(resourcePath, UIUtils.class);
}
/**
* Gets the image icon loaded from the given resource path.
*
* @param resourcePath the resource path
* @param callerClass the class which calls this method and therefore provides the class loader for the requested resource
*
* @return an image icon loaded from the given resource path or <code>null</code> if it could not be found
* @since 4.0
*/
public static ImageIcon loadImageIcon(String resourcePath, Class callerClass) {
if (StringUtils.isNotNullAndNotEmpty(resourcePath)) {
URL location = getImageURL(resourcePath, callerClass);
return (location != null) ? new ImageIcon(location) : null;
}
return null;
}
/**
* Gets the location of the given image resource path as an URL.
* <p>Note that this method only works for images found in the classpath of the class loader which loaded this {@link UIUtils} class.
* If you are not sure, you should better use {@link #getImageURL(String, Class)}.</p>
*
* @param resourcePath the resource path
*
* @return an URL representing the given resource path or <code>null</code> if it could not be found
*/
public static URL getImageURL(String resourcePath) {
return getImageURL(resourcePath, UIUtils.class);
}
/**
* Gets the location of the given image resource path as an URL.
*
* @param resourcePath the resource path
* @param callerClass the class which calls this method and therefore provides the class loader for the requested resource
*
* @return an URL representing the given resource path or <code>null</code> if it could not be found
* @since 4.0
*/
public static URL getImageURL(String resourcePath, Class callerClass) {
String absResourcePath = resourcePath;
if (!absResourcePath.startsWith("/")) {
absResourcePath = IMAGE_RESOURCE_PATH + resourcePath;
}
return callerClass.getResource(absResourcePath);
}
/**
* Returns the (main) screen's size in pixels.
*/
public static Dimension getScreenSize() {
return Toolkit.getDefaultToolkit().getScreenSize();
}
/**
* Returns the (main) screen's width in pixels.
*/
public static int getScreenWidth() {
return getScreenSize().width;
}
/**
* Returns the (main) screen's height in pixels.
*/
public static int getScreenHeight() {
return getScreenSize().height;
}
/**
* Centers the given component within the screen area.
* <p> The method performs the alignment by setting a newly computed location for the component. It does not alter
* the component's size.
*
* @param comp the component whose location is to be altered
*
* @throws IllegalArgumentException if the component is <code>null</code>
*/
public static void centerComponent(Component comp) {
centerComponent(comp, null);
}
/**
* Centers the given component over another component.
* <p> The method performs the alignment by setting a newly computed location for the component. It does not alter
* the component's size.
*
* @param comp the component whose location is to be altered
* @param alignComp the component used for the alignment of the first component, if <code>null</code> the component
* is ceneterd within the screen area
*
* @throws IllegalArgumentException if the component is <code>null</code>
*/
public static void centerComponent(Component comp, Component alignComp) {
if (comp == null) {
throw new IllegalArgumentException("comp must not be null");
}
Dimension compSize = comp.getSize();
Dimension screenSize = getScreenSize();
int x1, y1;
if (alignComp != null) {
Point alignCompOffs = alignComp.getLocation();
Dimension alignCompSize = alignComp.getSize();
x1 = alignCompOffs.x + (alignCompSize.width - compSize.width) / 2;
y1 = alignCompOffs.y + (alignCompSize.height - compSize.height) / 2;
} else {
x1 = (screenSize.width - compSize.width) / 2;
y1 = (screenSize.height - compSize.height) / 2;
}
int x2 = x1 + compSize.width;
int y2 = y1 + compSize.height;
if (x2 >= screenSize.width) {
x1 = screenSize.width - compSize.width - 1;
}
if (y2 >= screenSize.height) {
y1 = screenSize.height - compSize.height - 1;
}
if (x1 < 0) {
x1 = 0;
}
if (y1 < 0) {
y1 = 0;
}
comp.setLocation(x1, y1);
}
/**
* Prevent's instantiation.
*/
private UIUtils() {
}
/**
* Ensures that the popup menue is allways inside the application frame
*/
public static void showPopup(JPopupMenu popup, MouseEvent event) {
if (popup == null) {
return;
}
final Component component = event.getComponent();
final Point point = event.getPoint();
popup.show(component, point.x, point.y);
}
public static Window getRootWindow(Component component) {
Guardian.assertNotNull("component", component);
do {
Component parent = component.getParent();
if (parent == null && component instanceof Window) {
return (Window) component;
}
component = parent;
} while (component != null);
return null;
}
public static Border createGroupBorder(String title) {
return BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED),
title);
}
public static Frame getRootFrame(Component component) {
Guardian.assertNotNull("component", component);
final Window window = getRootWindow(component);
return (window instanceof Frame) ? (Frame) window : null;
}
public static JFrame getRootJFrame(Component component) {
Guardian.assertNotNull("component", component);
final Window window = getRootWindow(component);
return (window instanceof JFrame) ? (JFrame) window : null;
}
public static Cursor setRootFrameWaitCursor(Component component) {
return setRootFrameCursor(component, Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
}
public static Cursor setRootFrameDefaultCursor(Component component) {
return setRootFrameCursor(component, Cursor.getDefaultCursor());
}
public static Cursor setRootFrameCursor(Component component, Cursor newCursor) {
Guardian.assertNotNull("component", component);
Frame frame = getRootFrame(component);
if (frame == null && component instanceof Frame) {
frame = (Frame) component;
}
Cursor oldCursor = null;
if (frame != null) {
oldCursor = frame.getCursor();
if (newCursor != null) {
frame.setCursor(newCursor);
} else {
frame.setCursor(Cursor.getDefaultCursor());
}
}
return oldCursor;
}
public static JMenu findMenu(JMenuBar menuBar, String name, boolean deepSearch) {
int n = menuBar.getMenuCount();
for (int i = 0; i < n; i++) {
JMenu menu = menuBar.getMenu(i);
if (name.equals(menu.getName())) {
return menu;
}
}
if (deepSearch) {
for (int i = 0; i < n; i++) {
JMenu menu = menuBar.getMenu(i);
JMenu subMenu = findSubMenu(menu.getPopupMenu(), name);
if (subMenu != null) {
return subMenu;
}
}
}
return null;
}
public static int findMenuPosition(JMenuBar menuBar, String name) {
int n = menuBar.getMenuCount();
for (int i = 0; i < n; i++) {
JMenu menu = menuBar.getMenu(i);
if (name.equals(menu.getName())) {
return i;
}
}
return -1;
}
public static int findMenuItemPosition(JPopupMenu popupMenu, String name) {
int n = popupMenu.getComponentCount();
for (int i = 0; i < n; i++) {
Component c = popupMenu.getComponent(i);
if (c instanceof JMenuItem) {
JMenuItem menuItem = (JMenuItem) c;
if (name.equals(menuItem.getName())) {
return i;
}
}
}
return -1;
}
public static JMenu findSubMenu(JPopupMenu popupMenu, String name) {
int n = popupMenu.getComponentCount();
for (int i = 0; i < n; i++) {
Component c = popupMenu.getComponent(i);
if (c instanceof JMenu) {
JMenu subMenu = (JMenu) c;
if (name.equals(subMenu.getName())) {
return subMenu;
}
subMenu = findSubMenu(subMenu.getPopupMenu(), name);
if (subMenu != null) {
return subMenu;
}
}
}
return null;
}
public static String getUniqueFrameTitle(final JInternalFrame[] frames, final String titleBase) {
if (frames.length == 0) {
return titleBase;
}
String[] titles = new String[frames.length];
for (int i = 0; i < frames.length; i++) {
JInternalFrame frame = frames[i];
titles[i] = frame.getTitle();
}
if (!ArrayUtils.isMemberOf(titleBase, titles)) {
return titleBase;
}
for (int i = 0; i < frames.length; i++) {
final String title = titleBase + " (" + (i + 2) + ")";
if (!ArrayUtils.isMemberOf(title, titles)) {
return title;
}
}
return titleBase + " (" + (frames.length + 1) + ")";
}
public static JSpinner createSpinner(final Parameter param, final Number spinnerStep, final String formatPattern) {
final Number v = (Number) param.getValue();
final ParamProperties properties = param.getProperties();
final Comparable min = (Comparable) properties.getMinValue();
final Comparable max = (Comparable) properties.getMaxValue();
final Double bigStep = spinnerStep.doubleValue() * 10;
final JSpinner spinner = createSpinner(v, min, max, spinnerStep, bigStep, formatPattern);
spinner.setName(properties.getLabel());
spinner.addChangeListener(e -> param.setValue(spinner.getValue(), null));
param.addParamChangeListener(event -> spinner.setValue(param.getValue()));
param.getEditor().getEditorComponent().addPropertyChangeListener("enabled",
evt -> spinner.setEnabled(((Boolean) evt.getNewValue()).booleanValue()));
properties.addPropertyChangeListener(evt -> {
if (ParamProperties.MAXVALUE_KEY.equals(evt.getPropertyName())) {
((SpinnerNumberModel) spinner.getModel()).setMaximum((Comparable) properties.getMaxValue());
}
if (ParamProperties.MINVALUE_KEY.equals(evt.getPropertyName())) {
((SpinnerNumberModel) spinner.getModel()).setMinimum((Comparable) properties.getMinValue());
}
});
final JSpinner.NumberEditor editor = ((JSpinner.NumberEditor) spinner.getEditor());
final JFormattedTextField textField = editor.getTextField();
textField.addFocusListener(new FocusListener() {
public void focusGained(FocusEvent e) {
textField.selectAll();
}
public void focusLost(FocusEvent e) {
}
});
return spinner;
}
public static JSpinner createDoubleSpinner(final double value, final double rangeMinimum,
final double rangeMaximum,
final double stepSize, final double bigStepSize,
final String formatPattern) {
final SpinnerNumberModel numberModel = new SpinnerNumberModel(value, rangeMinimum, rangeMaximum, stepSize);
final JSpinner spinner = new JSpinner(numberModel);
final JComponent editor = spinner.getEditor();
if (editor instanceof JSpinner.NumberEditor) {
JSpinner.NumberEditor numberEditor = (JSpinner.NumberEditor) editor;
final DecimalFormat format = numberEditor.getFormat();
format.applyPattern(formatPattern);
numberEditor.getTextField().setColumns(8);
}
final Double sss = stepSize;
final Double bss = bigStepSize;
final String bigDec = "dec++";
final String bigInc = "inc++";
final InputMap inputMap = spinner.getInputMap();
final ActionMap actionMap = spinner.getActionMap();
// big increase with "PAGE_UP" key
inputMap.put(KeyStroke.getKeyStroke("PAGE_UP"), bigInc);
actionMap.put(bigInc, new AbstractAction() {
public void actionPerformed(ActionEvent e) {
numberModel.setStepSize(bss);
numberModel.setValue(numberModel.getNextValue());
numberModel.setStepSize(sss);
}
});
// big decrease with "PAGE_UP" key
inputMap.put(KeyStroke.getKeyStroke("PAGE_DOWN"), bigDec);
actionMap.put(bigDec, new AbstractAction() {
public void actionPerformed(ActionEvent e) {
numberModel.setStepSize(bss);
numberModel.setValue(numberModel.getPreviousValue());
numberModel.setStepSize(sss);
}
});
return spinner;
}
public static JSpinner createSpinner(final Number value, final Comparable rangeMinimum, final Comparable rangeMaximum,
final Number stepSize, final Number bigStepSize, final String formatPattern) {
final SpinnerNumberModel numberModel = new SpinnerNumberModel(value, rangeMinimum, rangeMaximum, stepSize);
final JSpinner spinner = new JSpinner(numberModel);
final JComponent editor = spinner.getEditor();
if (editor instanceof JSpinner.NumberEditor) {
JSpinner.NumberEditor numberEditor = (JSpinner.NumberEditor) editor;
final DecimalFormat format = numberEditor.getFormat();
format.applyPattern(formatPattern);
numberEditor.getTextField().setColumns(8);
}
spinner.setValue(value);
final String bigDec = "dec++";
final String bigInc = "inc++";
final InputMap inputMap = spinner.getInputMap();
final ActionMap actionMap = spinner.getActionMap();
// big increase with "PAGE_UP" key
inputMap.put(KeyStroke.getKeyStroke("PAGE_UP"), bigInc);
actionMap.put(bigInc, new AbstractAction() {
public void actionPerformed(ActionEvent e) {
numberModel.setStepSize(bigStepSize);
numberModel.setValue(numberModel.getNextValue());
numberModel.setStepSize(stepSize);
}
});
// big decrease with "PAGE_UP" key
inputMap.put(KeyStroke.getKeyStroke("PAGE_DOWN"), bigDec);
actionMap.put(bigDec, new AbstractAction() {
public void actionPerformed(ActionEvent e) {
numberModel.setStepSize(bigStepSize);
numberModel.setValue(numberModel.getPreviousValue());
numberModel.setStepSize(stepSize);
}
});
return spinner;
}
}