/*
* Copyright (c) 2005-2016 Vincent Vandenschrick. All rights reserved.
*
* This file is part of the Jspresso framework.
*
* Jspresso 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 3 of the License, or
* (at your option) any later version.
*
* Jspresso 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 Jspresso. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jspresso.framework.util.swing;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JWindow;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import foxtrot.Job;
import foxtrot.Worker;
import org.jspresso.framework.util.exception.NestedRuntimeException;
/**
* A helper class for Swing.
*
* @author Vincent Vandenschrick
*/
public final class SwingUtil {
private static final double DARKER_COLOR_FACTOR = 0.93;
private static final boolean DISABLE_THREADING = false;
private static final String FORMATTED_TEXTFIELD_FONT_KEY = "FormattedTextField.font";
private static final String FORMATTED_TEXTFIELD_INACTIVE_BACKGROUND_KEY = "FormattedTextField.inactiveBackground";
private static final String PASSWORDFIELD_FONT_KEY = "PasswordField.font";
private static final String TEXTFIELD_FONT_KEY = "TextField.font";
private static final String TEXTFIELD_INACTIVE_BACKGROUND_KEY = "TextField.inactiveBackground";
private SwingUtil() {
// Helper class private constructor.
}
/**
* Make even and odd rows background colors slightly different in collection
* component (table, list, ...).
*
* @param background
* the base background color of the collection component (table,
* list, ...) on which this renderer is used.
* @param isSelected
* is the row selected ?
* @param row
* the row to render.
* @return the computed color
*/
public static Color computeEvenOddBackground(Color background,
boolean isSelected, int row) {
if (!isSelected) {
if (row % 2 == 1) {
return SwingUtil.getScaledColor(background, DARKER_COLOR_FACTOR);
}
return background;
}
return background.brighter();
}
/**
* Center a window on screen.
*
* @param w
* the window to center on screen.
*/
public static void centerInParent(Window w) {
Container parent = w.getParent();
if (parent != null) {
Dimension parentSize = parent.getSize();
w.setLocation(parent.getX() + (parentSize.width - w.getWidth()) / 2,
parent.getY() + (parentSize.height - w.getHeight()) / 2);
}
}
/**
* Center a window on screen.
*
* @param w
* the window to center on screen.
*/
public static void centerOnScreen(Window w) {
Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
w.setLocation((screenDim.width - w.getWidth()) / 2,
(screenDim.height - w.getHeight()) / 2);
}
/**
* Configures a jButton with default behaviour like the multi-click threshold.
*
* @param button
* the button to work on.
*/
public static void configureButton(JButton button) {
button.setMultiClickThreshhold(500);
}
/**
* Configures a text field so that it selects its content when getting focus by
* another mean than the mouse.
*
* @param textField
* the text field to work on.
*/
public static void enableSelectionOnFocusGained(final JTextField textField) {
textField.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent fe) {
if (fe.getOppositeComponent() != null) {
FocusGainedTask task = new FocusGainedTask(textField);
if (textField instanceof JFormattedTextField) {
SwingUtilities.invokeLater(task);
} else {
task.run();
}
}
}
@Override
public void focusLost(FocusEvent fe) {
if (!fe.isTemporary()) {
FocusLostTask task = new FocusLostTask(textField);
if (textField instanceof JFormattedTextField) {
SwingUtilities.invokeLater(task);
} else {
task.run();
}
}
}
});
textField.getDocument().addDocumentListener(
new TfDocumentListener(textField));
}
/**
* Retrieves the first contained component of a certain type.
*
* @param component
* the component to start from.
* @param childComponentType
* the type of the component to look for.
* @return the first contained component of the looked for type or null if
* none.
*/
public static Component getFirstChildComponentOfType(Component component,
Class<? extends JComponent> childComponentType) {
if (childComponentType.isAssignableFrom(component.getClass())) {
return component;
}
if (component instanceof Container) {
Component[] children = ((Container) component).getComponents();
for (Component child : children) {
Component childResult = getFirstChildComponentOfType(child,
childComponentType);
if (childResult != null) {
return childResult;
}
}
}
return null;
}
/**
* Make a color scaled using a defined factor.
*
* @param color
* the color to scale.
* @param factor
* the factor to use.
* @return the scaled color.
*/
public static Color getScaledColor(Color color, double factor) {
if (color == null) {
return null;
}
if (factor <= 1) {
return new Color(Math.max((int) (color.getRed() * factor), 0), Math.max(
(int) (color.getGreen() * factor), 0), Math.max(
(int) (color.getBlue() * factor), 0), color.getAlpha());
}
int r = color.getRed();
int g = color.getGreen();
int b = color.getBlue();
int i = (int) (1.0 / (factor - 1));
if (r == 0 && g == 0 && b == 0) {
return new Color(i, i, i);
}
if (r > 0 && r < i) {
r = i;
}
if (g > 0 && g < i) {
g = i;
}
if (b > 0 && b < i) {
b = i;
}
return new Color(Math.min((int) (r / (2 - factor)), 255), Math.min(
(int) (g / (2 - factor)), 255), Math.min((int) (b / (2 - factor)), 255));
}
/**
* Gets the visible parent window.
*
* @param component
* the component to start from
* @return the visible parent window or null.
*/
public static Window getVisibleWindow(Component component) {
if (component instanceof JWindow) {
return (JWindow) component;
}
Window w = SwingUtilities.getWindowAncestor(component);
if (w != null && !w.isVisible() && w.getParent() != null) {
return getVisibleWindow(w.getParent());
}
return w;
}
/**
* Gets the window or the internal frame holding the component.
*
* @param component
* the component to look the window or internal frame for.
* @return the window (frame or dialog) or the internal frame in the component
* hierarchy.
*/
public static Component getWindowOrInternalFrame(Component component) {
if ((component instanceof Window) || (component instanceof JInternalFrame)) {
return component;
}
if (component != null) {
return getWindowOrInternalFrame(component.getParent());
}
return null;
}
/**
* Workaround for Bug#5063999.
*/
public static void installDefaults() {
String defaultlaf = System.getProperty("swing.defaultlaf");
if (defaultlaf == null || defaultlaf.length() == 0) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | UnsupportedLookAndFeelException | IllegalAccessException | InstantiationException ex) {
// NO-OP
}
}
JFrame.setDefaultLookAndFeelDecorated(true);
JDialog.setDefaultLookAndFeelDecorated(true);
UIManager.put(FORMATTED_TEXTFIELD_FONT_KEY,
UIManager.get(TEXTFIELD_FONT_KEY));
UIManager.put(FORMATTED_TEXTFIELD_INACTIVE_BACKGROUND_KEY,
UIManager.get(TEXTFIELD_INACTIVE_BACKGROUND_KEY));
UIManager.put(PASSWORDFIELD_FONT_KEY, UIManager.get(TEXTFIELD_FONT_KEY));
// try {
// UIManager.put(LafWidget.TABBED_PANE_PREVIEW_PAINTER,
// new DefaultTabPreviewPainter() {
//
// /**
// * {@inheritDoc}
// */
// @Override
// public TabOverviewKind getOverviewKind(JTabbedPane tabPane) {
// return TabOverviewKind.ROUND_CAROUSEL;
// // return TabOverviewKind.MENU_CAROUSEL;
// }
// });
// UIManager.put(LafWidget.COMPONENT_PREVIEW_PAINTER,
// new DefaultPreviewPainter());
// UIManager.put(LafWidget.TEXT_EDIT_CONTEXT_MENU, Boolean.TRUE);
// // UIManager.put(LafWidget.COMBO_BOX_NO_AUTOCOMPLETION, Boolean.TRUE);
// } catch (Throwable ignored) {
// // substance may not be available.
// }
}
/**
* Tests whether the component passed in parameter is used as an editor.
*
* @param comp
* the component to test.
* @return true if the component is currently used as an editor.
*/
public static boolean isUsedAsEditor(Component comp) {
boolean usedAsEditor = false;
Container parent = comp.getParent();
while (parent != null && !usedAsEditor) {
if (parent instanceof JTable) {
usedAsEditor = true;
}
parent = parent.getParent();
}
return usedAsEditor;
}
/**
* Executes a job avoiding the common swing UI freeze.
*
* @param foxtrotJob
* the potentially long running job to execute.
* @return the job execution result.
*/
public static Object performLongOperation(Job foxtrotJob) {
if (DISABLE_THREADING) {
return foxtrotJob.run();
}
return Worker.post(foxtrotJob);
}
/**
* Tests whether in swing event dispatch thread. If not, use SwingUtilities to
* invoke runnable and wait.
*
* @param runnable
* the runnable operation which updates the GUI.
*/
public static void updateSwingGui(Runnable runnable) {
if (DISABLE_THREADING) {
runnable.run();
} else {
if (SwingUtilities.isEventDispatchThread()) {
runnable.run();
} else {
try {
SwingUtilities.invokeAndWait(runnable);
} catch (InterruptedException ex) {
throw new NestedRuntimeException(ex);
} catch (InvocationTargetException ex) {
if (ex.getCause() instanceof RuntimeException) {
throw (RuntimeException) ex.getCause();
}
throw new NestedRuntimeException(ex.getCause());
}
// SwingUtilities.invokeLater(runnable);
}
}
}
private static final class FocusGainedTask implements Runnable {
private final JTextField textField;
/**
* Constructs a new {@code FocusGainedTask} instance.
*
* @param textField
* the text field to run on.
*/
public FocusGainedTask(JTextField textField) {
this.textField = textField;
}
/**
* {@inheritDoc}
*/
@Override
public void run() {
if (!isUsedAsEditor(textField)) {
// textField.selectAll();
if (textField.getDocument() != null) {
textField.setCaretPosition(textField.getDocument().getLength());
textField.moveCaretPosition(0);
}
}
}
}
private static final class FocusLostTask implements Runnable {
private final JTextField textField;
/**
* Constructs a new {@code FocusLostTask} instance.
*
* @param textField
* the text field to run on.
*/
public FocusLostTask(JTextField textField) {
this.textField = textField;
}
/**
* {@inheritDoc}
*/
@Override
public void run() {
if (!isUsedAsEditor(textField)) {
if (textField.getText().length() > 0) {
// textField.setCaretPosition(textField.getText().length());
textField.setCaretPosition(0);
}
}
}
}
private static class TfDocumentListener implements DocumentListener {
private final JTextField textField;
/**
* Constructs a new {@code TfDocumentListener} instance.
*
* @param textField
* the text field to run on.
*/
protected TfDocumentListener(JTextField textField) {
this.textField = textField;
}
/**
* {@inheritDoc}
*/
@Override
public void changedUpdate(DocumentEvent e) {
if (!textField.hasFocus()) {
SwingUtilities.invokeLater(new FocusLostTask(textField));
}
}
/**
* {@inheritDoc}
*/
@Override
public void insertUpdate(DocumentEvent e) {
if (!textField.hasFocus()) {
SwingUtilities.invokeLater(new FocusLostTask(textField));
}
}
/**
* {@inheritDoc}
*/
@Override
public void removeUpdate(DocumentEvent e) {
if (!textField.hasFocus()) {
SwingUtilities.invokeLater(new FocusLostTask(textField));
}
}
}
}