/*
* The Unified Mapping Platform (JUMP) is an extensible, interactive GUI for
* visualizing and manipulating spatial features with geometry and attributes.
*
* Copyright (C) 2003 Vivid Solutions
*
* 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.
*
* For more information, contact:
*
* Vivid Solutions
* Suite #1A
* 2328 Government Street
* Victoria BC V8T 5G5
* Canada
*
* (250)385-6040
* www.vividsolutions.com
*/
package com.vividsolutions.jump.workbench.ui;
import com.vividsolutions.jts.util.Assert;
import com.vividsolutions.jump.I18N;
import com.vividsolutions.jump.util.FileUtil;
import com.vividsolutions.jump.util.StringUtil;
import com.vividsolutions.jump.workbench.datasource.FileDataSourceQueryChooser;
import com.vividsolutions.jump.workbench.ui.images.IconLoader;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.Transferable;
import java.awt.event.*;
import java.awt.font.TextLayout;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.filechooser.FileFilter;
import javax.swing.plaf.basic.BasicComboBoxEditor;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn;
//<<TODO:NAMING>> Perhaps rename to WorkbenchUtilities and move to workbench
// package? [Jon Aquino]
public class GUIUtil {
public final static String dbf = "dbf";
public final static String dbfDesc = "DBF";
public final static String fme = "fme";
public final static String fmeDesc = "FME GML";
public final static String gml = "gml";
public final static String gmlDesc = "GML";
//<<TODO:REFACTORING>> If these constants are only used by descendants of
//AbstractDriver, they should be moved to AbstractDriver. GUIUtilities is
//supposed to be very generic. [Jon Aquino]
public final static String jml = "jml";
public final static String jmlDesc = "JCS GML";
public final static String shp = "shp";
//<<TODO:NAMING>> "ESRI Shapefile" would be more precise. Is this what
// they
//are? [Jon Aquino]
public final static String shpDesc = "ESRI Shapefile";
public final static String shx = "shx";
public final static String shxDesc = "SHX";
public final static String wkt = "wkt";
public final static String wktDesc = "Well Known Text";
public final static String wktaDesc = "Well Known Text (Show Attribute)";
public final static String xml = "xml";
public final static String xmlDesc = "XML";
public static final FileFilter ALL_FILES_FILTER = new FileFilter() {
public boolean accept(File f) {
return true;
}
public String getDescription() {
return "All Files";
}
};
public GUIUtil() {
}
/**
* Returns a string suitable for embedding as HTML. That is, all characters
* which have a special meaning in HTML are escaped as character codes.
*
* <p>
* Based on code from Jason Sherman. See
* http://www.w3schools.com/html/html_asciiref.asp
* </p>
*/
public final static String escapeHTML(String value, boolean escapeSpaces,
boolean escapeNewlines) {
if (value == null) {
return (null);
}
char[] content = new char[value.length()];
value.getChars(0, value.length(), content, 0);
StringBuffer result = new StringBuffer();
for (int i = 0; i < content.length; i++) {
switch (content[i]) {
case ' ':
result.append(escapeSpaces ? " " : " ");
break;
//Added \n [Jon Aquino]
case '\n':
result.append(escapeNewlines ? "<BR>" : "\n");
break;
case '!':
result.append("!");
break;
case '"':
result.append(""");
break;
case '#':
result.append("#");
break;
case '$':
result.append("$");
break;
case '%':
result.append("%");
break;
case '&':
result.append("&");
break;
case '\'':
result.append("'");
break;
case '(':
result.append("(");
break;
case ')':
result.append(")");
break;
case '*':
result.append("*");
break;
case '+':
result.append("+");
break;
case ',':
result.append(",");
break;
case '-':
result.append("-");
break;
case '.':
result.append(".");
break;
case '/':
result.append("/");
break;
case ':':
result.append(":");
break;
case ';':
result.append(";");
break;
case '<':
result.append("<");
break;
case '=':
result.append("=");
break;
case '>':
result.append(">");
break;
case '?':
result.append("?");
break;
case '@':
result.append("@");
break;
case '[':
result.append("[");
break;
case '\\':
result.append("\");
break;
case ']':
result.append("]");
break;
case '^':
result.append("^");
break;
case '_':
result.append("_");
break;
case '`':
result.append("`");
break;
case '{':
result.append("{");
break;
case '|':
result.append("|");
break;
case '}':
result.append("}");
break;
case '~':
result.append("~");
break;
default:
result.append(content[i]);
}
}
return (result.toString());
}
/*
* Get the extension of a file e.g. txt
*/
public static String getExtension(File f) {
return FileUtil.getExtension(f);
}
public static Color alphaColor(Color color, int alpha) {
return new Color(color.getRed(), color.getGreen(), color.getBlue(),
alpha);
}
/**
* Centres the first component on the second
*
* @param componentToMove
* Description of the Parameter
* @param componentToCentreOn
* Description of the Parameter
*/
public static void centre(Component componentToMove,
Component componentToCentreOn) {
Dimension componentToCentreOnSize = componentToCentreOn.getSize();
componentToMove.setLocation(
componentToCentreOn.getX()
+ ((componentToCentreOnSize.width - componentToMove.getWidth()) / 2),
componentToCentreOn.getY()
+ ((componentToCentreOnSize.height - componentToMove.getHeight()) / 2));
}
/**
* Centres the component on the screen
*
* @param componentToMove
* Description of the Parameter
*/
public static void centreOnScreen(Component componentToMove) {
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
componentToMove.setLocation((screenSize.width - componentToMove
.getWidth()) / 2, (screenSize.height - componentToMove
.getHeight()) / 2);
}
/**
* Centres the component on its window
*
* @param componentToMove
* Description of the Parameter
*/
public static void centreOnWindow(Component componentToMove) {
centre(componentToMove, SwingUtilities
.windowForComponent(componentToMove));
}
/**
* Centrer the window on its parent window
*
* @param windowToMove window to be centered on its parent window
*/
public static void centreOnWindow(Window windowToMove) {
windowToMove.pack();
centre(windowToMove, SwingUtilities
.windowForComponent(windowToMove));
}
/**
* Sets the column widths based on the first row.
*
* @param table
* Description of the Parameter
*/
public static void chooseGoodColumnWidths(JTable table) {
//Without padding, columns are slightly narrow, and we get "...". [Jon
// Aquino]
final int PADDING = 5;
if (table.getModel().getRowCount() == 0) {
return;
}
for (int i = 0; i < table.getModel().getColumnCount(); i++) {
TableColumn column = table.getColumnModel().getColumn(i);
double headerWidth = table.getTableHeader().getDefaultRenderer()
.getTableCellRendererComponent(table,
table.getModel().getColumnName(i), false, false, 0,
i).getPreferredSize().getWidth()
+ PADDING;
double valueWidth = 10; // default in case of error
try {
valueWidth = table.getCellRenderer(0, i)
.getTableCellRendererComponent(table,
table.getModel().getValueAt(0, i), false,
false, 0, i).getPreferredSize().getWidth()
+ PADDING;
} catch (Exception ex) {
// ignore the exception, since we can easily choose a default
// width
}
//Limit column width to 200 pixels.
int width = Math.min(200, Math.max((int) headerWidth,
(int) valueWidth));
column.setPreferredWidth(width);
//Need to set the actual width too, otherwise actual width may end
//up a bit less than the preferred width. [Jon Aquino]
column.setWidth(width);
}
}
public static JFileChooser createJFileChooserWithExistenceChecking() {
return new JFileChooser() {
public void approveSelection() {
File[] files = selectedFiles(this);
if (files.length == 0) {
return;
}
for (int i = 0; i < files.length; i++) {
if (!files[i].exists() && !files[i].isFile()) {
return;
}
}
super.approveSelection();
}
};
}
public static JFileChooser createJFileChooserWithOverwritePrompting() {
return new FileChooserWithOverwritePrompting();
}
public static JFileChooser createJFileChooserWithOverwritePrompting(String ext) {
return new FileChooserWithOverwritePrompting(ext);
}
public static class FileChooserWithOverwritePrompting extends JFileChooser {
private String ext;
/**
* @param ext the default extension for files
*/
public FileChooserWithOverwritePrompting(String ext) {
this.ext = ext;
}
public FileChooserWithOverwritePrompting() {
// no extension set
}
public void approveSelection() {
if (selectedFiles(this).length != 1) {
return;
}
File selectedFile = selectedFile();
if (selectedFile.exists() && !selectedFile.isFile()) {
return;
}
if (selectedFile.exists() ||
(ext != null && (!selectedFile.toString().endsWith(ext)) &&
new File(selectedFile.toString() + "." + ext).exists())) {
int response = JOptionPane.showConfirmDialog(this, "The file "
+ selectedFile.getName() + " already exists. Do you "
+ "want to replace the existing file?", "JUMP",
JOptionPane.YES_NO_OPTION);
if (response != JOptionPane.YES_OPTION) {
return;
}
}
super.approveSelection();
}
protected File selectedFile() {
return selectedFiles(this)[0];
}
}
public static void doNotRoundDoubles(JTable table) {
table.setDefaultRenderer(Double.class, new DefaultTableCellRenderer() {
public void setValue(Object value) {
setText((value == null) ? "" : ("" + value));
}
{
setHorizontalAlignment(SwingConstants.RIGHT);
}
});
}
/**
* Workaround for Java Bug 4648654 "REGRESSION: Editable JComboBox focus
* misbehaves under Windows look and feel, proposed by Kleopatra
* (fastegal@addcom.de). Also see Java Bug 4673880 "REGRESSION: Modified
* editable JComboBox in Windows LAF does not release focus." This bug
* started occurring in Java 1.4.0.
*
* @param cb
* Description of the Parameter
*/
public static void fixEditableComboBox(JComboBox cb) {
Assert.isTrue(cb.isEditable());
if (!UIManager.getLookAndFeel().getName().equals("Windows")) {
return;
}
cb.setEditor(new BasicComboBoxEditor() {
public void setItem(Object item) {
super.setItem(item);
editor.selectAll();
}
});
}
public static void handleThrowable(final Throwable t, final Component parent) {
try {
//<<TODO:UI>> A humane interface does not pop up an error dialog,
// as that interrupts
//the user's work. Rather, error messages are displayed
// modelessly. See the book
//"Humane Interfaces" (Raskin 2000) [Jon Aquino]
SwingUtilities.invokeLater(new Runnable() {
public void run() {
t.printStackTrace(System.out);
JOptionPane.showMessageDialog(parent, StringUtil.split(t
.toString(), 80), "Exception",
JOptionPane.ERROR_MESSAGE);
}
});
} catch (Throwable t2) {
t2.printStackTrace(System.out);
}
}
/**
* GUI operations should be performed only on the AWT event dispatching
* thread. Blocks until the Runnable is finished.
*/
public static void invokeOnEventThread(Runnable r)
throws InterruptedException, InvocationTargetException {
if (SwingUtilities.isEventDispatchThread()) {
r.run();
} else {
SwingUtilities.invokeAndWait(r);
}
}
public static String nameWithoutExtension(File file) {
String name = file.getName();
int dotPosition = name.indexOf('.');
return (dotPosition < 0) ? name : name.substring(0, dotPosition);
}
public static void removeChoosableFileFilters(JFileChooser fc) {
FileFilter[] filters = fc.getChoosableFileFilters();
for (int i = 0; i < filters.length; i++) {
fc.removeChoosableFileFilter(filters[i]);
}
return;
}
/**
* @param extensions
* e.g. txt
*/
public static FileFilter createFileFilter(final String description,
final String[] extensions) {
return new FileFilter() {
public boolean accept(File f) {
if (f.isDirectory()) {
return true;
}
for (int i = 0; i < extensions.length; i++) {
if (GUIUtil.getExtension(f).equalsIgnoreCase(extensions[i])) {
return true;
}
}
return false;
}
public String getDescription() {
ArrayList extensionStrings = new ArrayList();
for (int i = 0; i < extensions.length; i++) {
extensionStrings.add("*." + extensions[i]);
}
return description
+ " ("
+ StringUtil.replaceAll(StringUtil
.toCommaDelimitedString(extensionStrings), ",",
";") + ")";
}
};
}
/**
* @param color
* a Color with possibly an alpha less than 255
* @return a Color with alpha equal to 255, but equivalent to the original
* translucent colour on a white background
*/
public static Color toSimulatedTransparency(Color color) {
//My guess, but it seems to work! [Jon Aquino]
return new Color(
color.getRed()
+ (int) (((255 - color.getRed()) * (255 - color
.getAlpha())) / 255d), color.getGreen()
+ (int) (((255 - color.getGreen()) * (255 - color
.getAlpha())) / 255d), color.getBlue()
+ (int) (((255 - color.getBlue()) * (255 - color
.getAlpha())) / 255d));
}
public static String truncateString(String s, int maxLength) {
if (s.length() < maxLength) {
return s;
}
return s.substring(0, maxLength - 3) + "...";
}
public static Point2D subtract(Point2D a, Point2D b) {
return new Point2D.Double(a.getX() - b.getX(), a.getY() - b.getY());
}
public static Point2D add(Point2D a, Point2D b) {
return new Point2D.Double(a.getX() + b.getX(), a.getY() + b.getY());
}
public static Point2D multiply(Point2D v, double x) {
return new Point2D.Double(v.getX() * x, v.getY() * x);
}
/**
* The JVM's clipboard implementation is buggy (see bugs 4644554 and 4522198
* in Sun's Java bug database). This method is a workaround that returns
* null if an exception is thrown, as suggested in the bug reports.
*/
public static Transferable getContents(Clipboard clipboard) {
try {
return clipboard.getContents(null);
} catch (Throwable t) {
return null;
}
}
/**
* Returns the distance from the baseline to the top of the text's bounding
* box. Unlike the usual ascent, which is independent of the actual text.
* Note that "True ascent" is not a standard term.
*/
public static double trueAscent(TextLayout layout) {
return -layout.getBounds().getY();
}
public static ImageIcon resize(ImageIcon icon, int extent) {
return new ImageIcon(icon.getImage().getScaledInstance(extent, extent,
Image.SCALE_FAST));
}
/**
* Resizes icon to 16 x 16.
*/
public static ImageIcon toSmallIcon(ImageIcon icon) {
return resize(icon, 16);
}
public static int swingThreadPriority() {
final Int i = new Int();
try {
invokeOnEventThread(new Runnable() {
public void run() {
i.i = Thread.currentThread().getPriority();
}
});
} catch (InvocationTargetException e) {
Assert.shouldNeverReachHere();
} catch (InterruptedException e) {
Assert.shouldNeverReachHere();
}
return i.i;
}
/**
* Fix for Sun Java Bug 4398733: if you click in an inactive JInternalFrame,
* the mousePressed and mouseReleased events will be fired, but not the
* mouseClicked event.
*/
public static void fixClicks(final Component c) {
//This is a time bomb because when (if?) Sun fixes the bug, this
// method will
//add an extra click. We should put an if statement here that
// immediately
//returns if the Java version is greater than or equal to that in
// which the bug
//is fixed. Problem is, we don't know what that version will be. [Jon
// Aquino]
c.addMouseListener(new MouseListener() {
public void mousePressed(MouseEvent e) {
add(e);
}
public void mouseExited(MouseEvent e) {
add(e);
}
public void mouseClicked(MouseEvent e) {
add(e);
}
public void mouseEntered(MouseEvent e) {
add(e);
}
private MouseEvent event(int i) {
return (MouseEvent) events.get(i);
}
public void mouseReleased(MouseEvent e) {
add(e);
if ((events.size() == 4)
&& (event(0).getID() == MouseEvent.MOUSE_PRESSED)
&& (event(1).getID() == MouseEvent.MOUSE_EXITED)
&& (event(2).getID() == MouseEvent.MOUSE_ENTERED)) {
c.dispatchEvent(new MouseEvent(c, MouseEvent.MOUSE_CLICKED,
System.currentTimeMillis(), e.getModifiers(), e
.getX(), e.getY(), e.getClickCount(), e
.isPopupTrigger()));
}
}
private void add(MouseEvent e) {
if (events.size() == 4) {
events.remove(0);
}
events.add(e);
}
private ArrayList events = new ArrayList();
});
}
/**
* Listens to all internal frames (current and future) in a JDesktopPane.
*/
public static void addInternalFrameListener(JDesktopPane pane,
final InternalFrameListener listener) {
JInternalFrame[] frames = pane.getAllFrames();
for (int i = 0; i < frames.length; i++) {
frames[i].addInternalFrameListener(listener);
}
pane.addContainerListener(new ContainerAdapter() {
public void componentAdded(ContainerEvent e) {
if (e.getChild() instanceof JInternalFrame) {
((JInternalFrame) e.getChild())
.removeInternalFrameListener(listener);
((JInternalFrame) e.getChild())
.addInternalFrameListener(listener);
}
}
});
}
public static DocumentListener toDocumentListener(
final ActionListener listener) {
return new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
listener.actionPerformed(new ActionEvent(e, 0, e.toString()));
}
public void removeUpdate(DocumentEvent e) {
listener.actionPerformed(new ActionEvent(e, 0, e.toString()));
}
public void changedUpdate(DocumentEvent e) {
listener.actionPerformed(new ActionEvent(e, 0, e.toString()));
}
};
}
public static ListDataListener toListDataListener(
final ActionListener listener) {
return new ListDataListener() {
public void intervalAdded(ListDataEvent e) {
listener.actionPerformed(new ActionEvent(e.getSource(), 0, e
.toString()));
}
public void intervalRemoved(ListDataEvent e) {
listener.actionPerformed(new ActionEvent(e.getSource(), 0, e
.toString()));
}
public void contentsChanged(ListDataEvent e) {
listener.actionPerformed(null);
}
};
}
public static InternalFrameListener toInternalFrameListener(
final ActionListener listener) {
return new InternalFrameListener() {
private void fireActionPerformed(InternalFrameEvent e) {
listener.actionPerformed(new ActionEvent(e.getSource(), e
.getID(), e.toString()));
}
public void internalFrameActivated(InternalFrameEvent e) {
fireActionPerformed(e);
}
public void internalFrameClosed(InternalFrameEvent e) {
fireActionPerformed(e);
}
public void internalFrameClosing(InternalFrameEvent e) {
fireActionPerformed(e);
}
public void internalFrameDeactivated(InternalFrameEvent e) {
fireActionPerformed(e);
}
public void internalFrameDeiconified(InternalFrameEvent e) {
fireActionPerformed(e);
}
public void internalFrameIconified(InternalFrameEvent e) {
fireActionPerformed(e);
}
public void internalFrameOpened(InternalFrameEvent e) {
fireActionPerformed(e);
}
};
}
/**
* Returns a Timer that fires once, after the delay. The delay can be
* restarted by restarting the Timer.
*/
public static Timer createRestartableSingleEventTimer(int delay,
ActionListener listener) {
Timer timer = new Timer(delay, listener);
timer.setCoalesce(true);
timer.setInitialDelay(delay);
timer.setRepeats(false);
return timer;
}
public static ValidatingTextField createSyncdTextField(JSlider s) {
int columns = (int) Math.ceil(Math.log(s.getMaximum()) / Math.log(10));
return createSyncdTextField(s, columns);
}
public static ValidatingTextField createSyncdTextField(JSlider s,
int columns) {
ValidatingTextField t = new ValidatingTextField(s.getValue() + "",
columns, SwingConstants.RIGHT,
ValidatingTextField.INTEGER_VALIDATOR,
new ValidatingTextField.CompositeCleaner(
new ValidatingTextField.Cleaner[] {
new ValidatingTextField.BlankCleaner(""
+ s.getMinimum()),
new ValidatingTextField.MinIntCleaner(s
.getMinimum()),
new ValidatingTextField.MaxIntCleaner(s
.getMaximum()) }));
sync(s, t);
syncEnabledStates(s, t);
return t;
}
/**
* @see #createSyncdTextField(JSlider s, int columns)
*/
public static void sync(final JSlider s, final ValidatingTextField t) {
t.setText("" + s.getValue());
final Boolean[] changing = new Boolean[] { Boolean.FALSE };
s.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
if (changing[0] == Boolean.TRUE) {
return;
}
changing[0] = Boolean.TRUE;
try {
t.setText("" + s.getValue());
} finally {
changing[0] = Boolean.FALSE;
}
}
});
t.getDocument().addDocumentListener(new DocumentListener() {
private void changed() {
if (changing[0] == Boolean.TRUE) {
return;
}
changing[0] = Boolean.TRUE;
try {
s.setValue(t.getInteger());
} finally {
changing[0] = Boolean.FALSE;
}
}
public void changedUpdate(DocumentEvent e) {
changed();
}
public void insertUpdate(DocumentEvent e) {
changed();
}
public void removeUpdate(DocumentEvent e) {
changed();
}
});
}
public static void syncEnabledStates(final JComponent c1,
final JComponent c2) {
c2.setEnabled(c1.isEnabled());
c1.addPropertyChangeListener("enabled", new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if (c1.isEnabled() == c2.isEnabled()) {
return;
}
c2.setEnabled(c1.isEnabled());
}
});
c2.addPropertyChangeListener("enabled", new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if (c1.isEnabled() == c2.isEnabled()) {
return;
}
c1.setEnabled(c2.isEnabled());
}
});
}
public static void sync(final JSlider s1, final JSlider s2) {
s2.setValue(s1.getValue());
Assert.isTrue(s1.getMinimum() == s2.getMinimum());
Assert.isTrue(s1.getMaximum() == s2.getMaximum());
final Boolean[] changing = new Boolean[] { Boolean.FALSE };
s1.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
if (changing[0] == Boolean.TRUE) {
return;
}
changing[0] = Boolean.TRUE;
try {
s2.setValue(s1.getValue());
} finally {
changing[0] = Boolean.FALSE;
}
}
});
s2.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
if (changing[0] == Boolean.TRUE) {
return;
}
changing[0] = Boolean.TRUE;
try {
s1.setValue(s2.getValue());
} finally {
changing[0] = Boolean.FALSE;
}
}
});
}
public static void sync(final JCheckBox c1, final JCheckBox c2) {
c2.setSelected(c1.isSelected());
final Boolean[] changing = new Boolean[] { Boolean.FALSE };
c1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (changing[0] == Boolean.TRUE) {
return;
}
changing[0] = Boolean.TRUE;
try {
c2.setSelected(c1.isSelected());
} finally {
changing[0] = Boolean.FALSE;
}
}
});
c2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (changing[0] == Boolean.TRUE) {
return;
}
changing[0] = Boolean.TRUE;
try {
c1.setSelected(c2.isSelected());
} finally {
changing[0] = Boolean.FALSE;
}
}
});
}
public static List items(JComboBox comboBox) {
ArrayList items = new ArrayList();
for (int i = 0; i < comboBox.getItemCount(); i++) {
items.add(comboBox.getItemAt(i));
}
return items;
}
/**
* Calls #doClick so that events are fired.
*/
public static void setSelectedWithClick(JCheckBox checkBox, boolean selected) {
checkBox.setSelected(!selected);
checkBox.doClick();
}
public static void setLocation(Component componentToMove,
Location location, Component other) {
Point p = new Point(
(int) other.getLocationOnScreen().getX()
+ (location.fromRight ? (other.getWidth()
- componentToMove.getWidth() - location.x)
: location.x), (int) other
.getLocationOnScreen().getY()
+ (location.fromBottom ? (other.getHeight()
- componentToMove.getHeight() - location.y)
: location.y));
if (!(componentToMove instanceof Window)) {
SwingUtilities.convertPointFromScreen(p, componentToMove
.getParent());
}
componentToMove.setLocation(p);
}
/**
* Highlights a given component with a given color. Great for GridBagLayout
* debugging.
*
*/
public static void highlightForDebugging(JComponent component, Color color) {
component.setBackground(color);
component.setBorder(BorderFactory.createMatteBorder(10, 10, 10, 10,
color));
}
public static Component topCard(Container c) {
Assert.isTrue(c.getLayout() instanceof CardLayout);
Component[] components = c.getComponents();
for (int i = 0; i < components.length; i++) {
if (components[i].isVisible()) {
return components[i];
}
}
Assert.shouldNeverReachHere();
return null;
}
/**
* Work around Java Bug 4437688 "JFileChooser.getSelectedFile() returns
* nothing when a file is selected" [Jon Aquino]
*/
public static File[] selectedFiles(JFileChooser chooser) {
return ((chooser.getSelectedFiles().length == 0) && (chooser
.getSelectedFile() != null)) ? new File[] { chooser
.getSelectedFile() } : chooser.getSelectedFiles();
}
public static ImageIcon toDisabledIcon(ImageIcon icon) {
return new ImageIcon(GrayFilter.createDisabledImage((icon).getImage()));
}
public static Component getDescendantOfClass(Class c, Container container) {
for (int i = 0; i < container.getComponentCount(); i++) {
if (c.isInstance(container.getComponent(i))) {
return container.getComponent(i);
}
if (container.getComponent(i) instanceof Container) {
Component descendant = getDescendantOfClass(c,
(Container) container.getComponent(i));
if (descendant != null) {
return descendant;
}
}
}
return null;
}
/**
* Ensures that the next frame is activated when #dispose is called
* explicitly, in JDK 1.4. JDK 1.3 didn't have this problem.
*/
public static void dispose(final JInternalFrame internalFrame,
JDesktopPane desktopPane) {
desktopPane.getDesktopManager().closeFrame(internalFrame);
internalFrame.dispose();
}
private static class Int {
public volatile int i;
}
public static class Location {
private int x;
private int y;
private boolean fromRight;
private boolean fromBottom;
/**
* Constructor taking an initial location, offset hint.
*
* @param fromBottom
* whether y is the number of pixels between the bottom edges
* of the toolbox and desktop pane, or between the top edges.
*/
public Location(int x, boolean fromRight, int y, boolean fromBottom) {
this.x = x;
this.y = y;
this.fromRight = fromRight;
this.fromBottom = fromBottom;
}
}
public static Cursor createCursorFromIcon(Image iconImage) {
//Don't use GUIUtil#resize, which uses SCALE_SMOOTH, which
//makes the check-mark icons chunky-looking.
//[2004-02-27]
ImageIcon icon = new ImageIcon(iconImage.getScaledInstance(12, 12,
Image.SCALE_REPLICATE));
ImageIcon basicCursor = IconLoader.icon("basic-cursor.png");
BufferedImage image = new BufferedImage(basicCursor.getIconWidth(),
basicCursor.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = (Graphics2D) image.getGraphics();
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
graphics.drawImage(basicCursor.getImage(), 0, 0, null);
graphics.drawImage(icon.getImage(), 10, 10, null);
return createCursor(image, new Point(0, 15));
}
public static Cursor createCursor(Image image, Point hotSpot) {
if (null == image) {
return Cursor.getDefaultCursor();
}
if (Toolkit.getDefaultToolkit().getBestCursorSize(32, 32).equals(
new Dimension(0, 0))) {
return Cursor.getDefaultCursor();
}
return Toolkit.getDefaultToolkit().createCustomCursor(image, hotSpot,
I18N.get("ui.GUIUtil.jump-workbench-custom-cursor"));
}
/**
* Based on Green, Roedy. "Java Glossary : focus".
* Available from http://mindprod.com/jgloss/focus.html.
* Internet; accessed 8 March 2004.
*/
public static JTextArea makeTabMoveFocus(JTextArea textArea) {
textArea.setFocusTraversalKeys(
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
new HashSet(Arrays.asList(new Object[] { KeyStroke
.getKeyStroke("TAB") })));
textArea.setFocusTraversalKeys(
KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, new HashSet(
Arrays.asList(new Object[] { KeyStroke
.getKeyStroke("shift TAB") })));
return textArea;
}
public static void shrinkFont(JComponent component) {
component.setFont(component.getFont().deriveFont(
(float) component.getFont().getSize() - 2));
}
}