0001: /*
0002: * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI for
0003: * visualizing and manipulating spatial features with geometry and attributes.
0004: *
0005: * Copyright (C) 2003 Vivid Solutions
0006: *
0007: * This program is free software; you can redistribute it and/or modify it under
0008: * the terms of the GNU General Public License as published by the Free Software
0009: * Foundation; either version 2 of the License, or (at your option) any later
0010: * version.
0011: *
0012: * This program is distributed in the hope that it will be useful, but WITHOUT
0013: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
0014: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
0015: * details.
0016: *
0017: * You should have received a copy of the GNU General Public License along with
0018: * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
0019: * Place - Suite 330, Boston, MA 02111-1307, USA.
0020: *
0021: * For more information, contact:
0022: *
0023: * Vivid Solutions
0024: * Suite #1A
0025: * 2328 Government Street
0026: * Victoria BC V8T 5G5
0027: * Canada
0028: *
0029: * (250)385-6040
0030: * www.vividsolutions.com
0031: */
0032: package com.vividsolutions.jump.workbench.ui;
0033:
0034: import com.vividsolutions.jts.util.Assert;
0035:
0036: import com.vividsolutions.jump.I18N;
0037: import com.vividsolutions.jump.util.FileUtil;
0038: import com.vividsolutions.jump.util.StringUtil;
0039: import com.vividsolutions.jump.workbench.datasource.FileDataSourceQueryChooser;
0040: import com.vividsolutions.jump.workbench.ui.images.IconLoader;
0041:
0042: import java.awt.*;
0043: import java.awt.datatransfer.Clipboard;
0044: import java.awt.datatransfer.Transferable;
0045: import java.awt.event.*;
0046: import java.awt.font.TextLayout;
0047: import java.awt.geom.Point2D;
0048: import java.awt.image.BufferedImage;
0049:
0050: import java.beans.PropertyChangeEvent;
0051: import java.beans.PropertyChangeListener;
0052:
0053: import java.io.File;
0054:
0055: import java.lang.reflect.InvocationTargetException;
0056:
0057: import java.util.ArrayList;
0058: import java.util.Arrays;
0059: import java.util.HashSet;
0060: import java.util.List;
0061:
0062: import javax.swing.*;
0063: import javax.swing.event.*;
0064: import javax.swing.filechooser.FileFilter;
0065: import javax.swing.plaf.basic.BasicComboBoxEditor;
0066: import javax.swing.table.DefaultTableCellRenderer;
0067: import javax.swing.table.TableColumn;
0068:
0069: //<<TODO:NAMING>> Perhaps rename to WorkbenchUtilities and move to workbench
0070: // package? [Jon Aquino]
0071: public class GUIUtil {
0072:
0073: public final static String dbf = "dbf";
0074:
0075: public final static String dbfDesc = "DBF";
0076:
0077: public final static String fme = "fme";
0078:
0079: public final static String fmeDesc = "FME GML";
0080:
0081: public final static String gml = "gml";
0082:
0083: public final static String gmlDesc = "GML";
0084:
0085: //<<TODO:REFACTORING>> If these constants are only used by descendants of
0086: //AbstractDriver, they should be moved to AbstractDriver. GUIUtilities is
0087: //supposed to be very generic. [Jon Aquino]
0088: public final static String jml = "jml";
0089:
0090: public final static String jmlDesc = "JCS GML";
0091:
0092: public final static String shp = "shp";
0093:
0094: //<<TODO:NAMING>> "ESRI Shapefile" would be more precise. Is this what
0095: // they
0096: //are? [Jon Aquino]
0097: public final static String shpDesc = "ESRI Shapefile";
0098:
0099: public final static String shx = "shx";
0100:
0101: public final static String shxDesc = "SHX";
0102:
0103: public final static String wkt = "wkt";
0104:
0105: public final static String wktDesc = "Well Known Text";
0106:
0107: public final static String wktaDesc = "Well Known Text (Show Attribute)";
0108:
0109: public final static String xml = "xml";
0110:
0111: public final static String xmlDesc = "XML";
0112:
0113: public static final FileFilter ALL_FILES_FILTER = new FileFilter() {
0114:
0115: public boolean accept(File f) {
0116: return true;
0117: }
0118:
0119: public String getDescription() {
0120: return "All Files";
0121: }
0122: };
0123:
0124: public GUIUtil() {
0125: }
0126:
0127: /**
0128: * Returns a string suitable for embedding as HTML. That is, all characters
0129: * which have a special meaning in HTML are escaped as character codes.
0130: *
0131: * <p>
0132: * Based on code from Jason Sherman. See
0133: * http://www.w3schools.com/html/html_asciiref.asp
0134: * </p>
0135: */
0136: public final static String escapeHTML(String value,
0137: boolean escapeSpaces, boolean escapeNewlines) {
0138: if (value == null) {
0139: return (null);
0140: }
0141:
0142: char[] content = new char[value.length()];
0143: value.getChars(0, value.length(), content, 0);
0144:
0145: StringBuffer result = new StringBuffer();
0146:
0147: for (int i = 0; i < content.length; i++) {
0148: switch (content[i]) {
0149: case ' ':
0150: result.append(escapeSpaces ? " " : " ");
0151:
0152: break;
0153:
0154: //Added \n [Jon Aquino]
0155: case '\n':
0156: result.append(escapeNewlines ? "<BR>" : "\n");
0157:
0158: break;
0159:
0160: case '!':
0161: result.append("!");
0162:
0163: break;
0164:
0165: case '"':
0166: result.append(""");
0167:
0168: break;
0169:
0170: case '#':
0171: result.append("#");
0172:
0173: break;
0174:
0175: case '$':
0176: result.append("$");
0177:
0178: break;
0179:
0180: case '%':
0181: result.append("%");
0182:
0183: break;
0184:
0185: case '&':
0186: result.append("&");
0187:
0188: break;
0189:
0190: case '\'':
0191: result.append("'");
0192:
0193: break;
0194:
0195: case '(':
0196: result.append("(");
0197:
0198: break;
0199:
0200: case ')':
0201: result.append(")");
0202:
0203: break;
0204:
0205: case '*':
0206: result.append("*");
0207:
0208: break;
0209:
0210: case '+':
0211: result.append("+");
0212:
0213: break;
0214:
0215: case ',':
0216: result.append(",");
0217:
0218: break;
0219:
0220: case '-':
0221: result.append("-");
0222:
0223: break;
0224:
0225: case '.':
0226: result.append(".");
0227:
0228: break;
0229:
0230: case '/':
0231: result.append("/");
0232:
0233: break;
0234:
0235: case ':':
0236: result.append(":");
0237:
0238: break;
0239:
0240: case ';':
0241: result.append(";");
0242:
0243: break;
0244:
0245: case '<':
0246: result.append("<");
0247:
0248: break;
0249:
0250: case '=':
0251: result.append("=");
0252:
0253: break;
0254:
0255: case '>':
0256: result.append(">");
0257:
0258: break;
0259:
0260: case '?':
0261: result.append("?");
0262:
0263: break;
0264:
0265: case '@':
0266: result.append("@");
0267:
0268: break;
0269:
0270: case '[':
0271: result.append("[");
0272:
0273: break;
0274:
0275: case '\\':
0276: result.append("\");
0277:
0278: break;
0279:
0280: case ']':
0281: result.append("]");
0282:
0283: break;
0284:
0285: case '^':
0286: result.append("^");
0287:
0288: break;
0289:
0290: case '_':
0291: result.append("_");
0292:
0293: break;
0294:
0295: case '`':
0296: result.append("`");
0297:
0298: break;
0299:
0300: case '{':
0301: result.append("{");
0302:
0303: break;
0304:
0305: case '|':
0306: result.append("|");
0307:
0308: break;
0309:
0310: case '}':
0311: result.append("}");
0312:
0313: break;
0314:
0315: case '~':
0316: result.append("~");
0317:
0318: break;
0319:
0320: default:
0321: result.append(content[i]);
0322: }
0323: }
0324:
0325: return (result.toString());
0326: }
0327:
0328: /*
0329: * Get the extension of a file e.g. txt
0330: */
0331: public static String getExtension(File f) {
0332: return FileUtil.getExtension(f);
0333: }
0334:
0335: public static Color alphaColor(Color color, int alpha) {
0336: return new Color(color.getRed(), color.getGreen(), color
0337: .getBlue(), alpha);
0338: }
0339:
0340: /**
0341: * Centres the first component on the second
0342: *
0343: * @param componentToMove
0344: * Description of the Parameter
0345: * @param componentToCentreOn
0346: * Description of the Parameter
0347: */
0348: public static void centre(Component componentToMove,
0349: Component componentToCentreOn) {
0350: Dimension componentToCentreOnSize = componentToCentreOn
0351: .getSize();
0352: componentToMove.setLocation(componentToCentreOn.getX()
0353: + ((componentToCentreOnSize.width - componentToMove
0354: .getWidth()) / 2), componentToCentreOn.getY()
0355: + ((componentToCentreOnSize.height - componentToMove
0356: .getHeight()) / 2));
0357: }
0358:
0359: /**
0360: * Centres the component on the screen
0361: *
0362: * @param componentToMove
0363: * Description of the Parameter
0364: */
0365: public static void centreOnScreen(Component componentToMove) {
0366: Dimension screenSize = Toolkit.getDefaultToolkit()
0367: .getScreenSize();
0368: componentToMove.setLocation((screenSize.width - componentToMove
0369: .getWidth()) / 2, (screenSize.height - componentToMove
0370: .getHeight()) / 2);
0371: }
0372:
0373: /**
0374: * Centres the component on its window
0375: *
0376: * @param componentToMove
0377: * Description of the Parameter
0378: */
0379: public static void centreOnWindow(Component componentToMove) {
0380: centre(componentToMove, SwingUtilities
0381: .windowForComponent(componentToMove));
0382: }
0383:
0384: /**
0385: * Sets the column widths based on the first row.
0386: *
0387: * @param table
0388: * Description of the Parameter
0389: */
0390: public static void chooseGoodColumnWidths(JTable table) {
0391: //Without padding, columns are slightly narrow, and we get "...". [Jon
0392: // Aquino]
0393: final int PADDING = 5;
0394:
0395: if (table.getModel().getRowCount() == 0) {
0396: return;
0397: }
0398:
0399: for (int i = 0; i < table.getModel().getColumnCount(); i++) {
0400: TableColumn column = table.getColumnModel().getColumn(i);
0401: double headerWidth = table.getTableHeader()
0402: .getDefaultRenderer()
0403: .getTableCellRendererComponent(table,
0404: table.getModel().getColumnName(i), false,
0405: false, 0, i).getPreferredSize().getWidth()
0406: + PADDING;
0407: double valueWidth = 10; // default in case of error
0408:
0409: try {
0410: valueWidth = table.getCellRenderer(0, i)
0411: .getTableCellRendererComponent(table,
0412: table.getModel().getValueAt(0, i),
0413: false, false, 0, i).getPreferredSize()
0414: .getWidth()
0415: + PADDING;
0416: } catch (Exception ex) {
0417: // ignore the exception, since we can easily choose a default
0418: // width
0419: }
0420:
0421: //Limit column width to 200 pixels.
0422: int width = Math.min(200, Math.max((int) headerWidth,
0423: (int) valueWidth));
0424: column.setPreferredWidth(width);
0425:
0426: //Need to set the actual width too, otherwise actual width may end
0427: //up a bit less than the preferred width. [Jon Aquino]
0428: column.setWidth(width);
0429: }
0430: }
0431:
0432: public static JFileChooser createJFileChooserWithExistenceChecking() {
0433: return new JFileChooser() {
0434:
0435: public void approveSelection() {
0436: File[] files = selectedFiles(this );
0437:
0438: if (files.length == 0) {
0439: return;
0440: }
0441:
0442: for (int i = 0; i < files.length; i++) {
0443: if (!files[i].exists() && !files[i].isFile()) {
0444: return;
0445: }
0446: }
0447:
0448: super .approveSelection();
0449: }
0450: };
0451: }
0452:
0453: public static JFileChooser createJFileChooserWithOverwritePrompting() {
0454: return new FileChooserWithOverwritePrompting();
0455: }
0456:
0457: public static JFileChooser createJFileChooserWithOverwritePrompting(
0458: String ext) {
0459: return new FileChooserWithOverwritePrompting(ext);
0460: }
0461:
0462: public static class FileChooserWithOverwritePrompting extends
0463: JFileChooser {
0464:
0465: private String ext;
0466:
0467: /**
0468: * @param ext the default extension for files
0469: */
0470: public FileChooserWithOverwritePrompting(String ext) {
0471: this .ext = ext;
0472: }
0473:
0474: public FileChooserWithOverwritePrompting() {
0475: // no extension set
0476: }
0477:
0478: public void approveSelection() {
0479: if (selectedFiles(this ).length != 1) {
0480: return;
0481: }
0482:
0483: File selectedFile = selectedFile();
0484:
0485: if (selectedFile.exists() && !selectedFile.isFile()) {
0486: return;
0487: }
0488:
0489: if (selectedFile.exists()
0490: || (ext != null
0491: && (!selectedFile.toString().endsWith(ext)) && new File(
0492: selectedFile.toString() + "." + ext)
0493: .exists())) {
0494: int response = JOptionPane.showConfirmDialog(this ,
0495: "The file " + selectedFile.getName()
0496: + " already exists. Do you "
0497: + "want to replace the existing file?",
0498: "JUMP", JOptionPane.YES_NO_OPTION);
0499:
0500: if (response != JOptionPane.YES_OPTION) {
0501: return;
0502: }
0503: }
0504:
0505: super .approveSelection();
0506: }
0507:
0508: protected File selectedFile() {
0509: return selectedFiles(this )[0];
0510: }
0511: }
0512:
0513: public static void doNotRoundDoubles(JTable table) {
0514: table.setDefaultRenderer(Double.class,
0515: new DefaultTableCellRenderer() {
0516:
0517: public void setValue(Object value) {
0518: setText((value == null) ? "" : ("" + value));
0519: }
0520:
0521: {
0522: setHorizontalAlignment(SwingConstants.RIGHT);
0523: }
0524: });
0525: }
0526:
0527: /**
0528: * Workaround for Java Bug 4648654 "REGRESSION: Editable JComboBox focus
0529: * misbehaves under Windows look and feel, proposed by Kleopatra
0530: * (fastegal@addcom.de). Also see Java Bug 4673880 "REGRESSION: Modified
0531: * editable JComboBox in Windows LAF does not release focus." This bug
0532: * started occurring in Java 1.4.0.
0533: *
0534: * @param cb
0535: * Description of the Parameter
0536: */
0537: public static void fixEditableComboBox(JComboBox cb) {
0538: Assert.isTrue(cb.isEditable());
0539:
0540: if (!UIManager.getLookAndFeel().getName().equals("Windows")) {
0541: return;
0542: }
0543:
0544: cb.setEditor(new BasicComboBoxEditor() {
0545:
0546: public void setItem(Object item) {
0547: super .setItem(item);
0548: editor.selectAll();
0549: }
0550: });
0551: }
0552:
0553: public static void handleThrowable(final Throwable t,
0554: final Component parent) {
0555: try {
0556: //<<TODO:UI>> A humane interface does not pop up an error dialog,
0557: // as that interrupts
0558: //the user's work. Rather, error messages are displayed
0559: // modelessly. See the book
0560: //"Humane Interfaces" (Raskin 2000) [Jon Aquino]
0561: SwingUtilities.invokeLater(new Runnable() {
0562:
0563: public void run() {
0564: t.printStackTrace(System.out);
0565: JOptionPane.showMessageDialog(parent, StringUtil
0566: .split(t.toString(), 80), "Exception",
0567: JOptionPane.ERROR_MESSAGE);
0568: }
0569: });
0570: } catch (Throwable t2) {
0571: t2.printStackTrace(System.out);
0572: }
0573: }
0574:
0575: /**
0576: * GUI operations should be performed only on the AWT event dispatching
0577: * thread. Blocks until the Runnable is finished.
0578: */
0579: public static void invokeOnEventThread(Runnable r)
0580: throws InterruptedException, InvocationTargetException {
0581: if (SwingUtilities.isEventDispatchThread()) {
0582: r.run();
0583: } else {
0584: SwingUtilities.invokeAndWait(r);
0585: }
0586: }
0587:
0588: public static String nameWithoutExtension(File file) {
0589: String name = file.getName();
0590: int dotPosition = name.indexOf('.');
0591:
0592: return (dotPosition < 0) ? name : name
0593: .substring(0, dotPosition);
0594: }
0595:
0596: public static void removeChoosableFileFilters(JFileChooser fc) {
0597: FileFilter[] filters = fc.getChoosableFileFilters();
0598:
0599: for (int i = 0; i < filters.length; i++) {
0600: fc.removeChoosableFileFilter(filters[i]);
0601: }
0602:
0603: return;
0604: }
0605:
0606: /**
0607: * @param extensions
0608: * e.g. txt
0609: */
0610: public static FileFilter createFileFilter(final String description,
0611: final String[] extensions) {
0612: return new FileFilter() {
0613:
0614: public boolean accept(File f) {
0615: if (f.isDirectory()) {
0616: return true;
0617: }
0618:
0619: for (int i = 0; i < extensions.length; i++) {
0620: if (GUIUtil.getExtension(f).equalsIgnoreCase(
0621: extensions[i])) {
0622: return true;
0623: }
0624: }
0625:
0626: return false;
0627: }
0628:
0629: public String getDescription() {
0630: ArrayList extensionStrings = new ArrayList();
0631:
0632: for (int i = 0; i < extensions.length; i++) {
0633: extensionStrings.add("*." + extensions[i]);
0634: }
0635:
0636: return description
0637: + " ("
0638: + StringUtil
0639: .replaceAll(
0640: StringUtil
0641: .toCommaDelimitedString(extensionStrings),
0642: ",", ";") + ")";
0643: }
0644: };
0645: }
0646:
0647: /**
0648: * @param color
0649: * a Color with possibly an alpha less than 255
0650: * @return a Color with alpha equal to 255, but equivalent to the original
0651: * translucent colour on a white background
0652: */
0653: public static Color toSimulatedTransparency(Color color) {
0654: //My guess, but it seems to work! [Jon Aquino]
0655: return new Color(color.getRed()
0656: + (int) (((255 - color.getRed()) * (255 - color
0657: .getAlpha())) / 255d), color.getGreen()
0658: + (int) (((255 - color.getGreen()) * (255 - color
0659: .getAlpha())) / 255d), color.getBlue()
0660: + (int) (((255 - color.getBlue()) * (255 - color
0661: .getAlpha())) / 255d));
0662: }
0663:
0664: public static String truncateString(String s, int maxLength) {
0665: if (s.length() < maxLength) {
0666: return s;
0667: }
0668:
0669: return s.substring(0, maxLength - 3) + "...";
0670: }
0671:
0672: public static Point2D subtract(Point2D a, Point2D b) {
0673: return new Point2D.Double(a.getX() - b.getX(), a.getY()
0674: - b.getY());
0675: }
0676:
0677: public static Point2D add(Point2D a, Point2D b) {
0678: return new Point2D.Double(a.getX() + b.getX(), a.getY()
0679: + b.getY());
0680: }
0681:
0682: public static Point2D multiply(Point2D v, double x) {
0683: return new Point2D.Double(v.getX() * x, v.getY() * x);
0684: }
0685:
0686: /**
0687: * The JVM's clipboard implementation is buggy (see bugs 4644554 and 4522198
0688: * in Sun's Java bug database). This method is a workaround that returns
0689: * null if an exception is thrown, as suggested in the bug reports.
0690: */
0691: public static Transferable getContents(Clipboard clipboard) {
0692: try {
0693: return clipboard.getContents(null);
0694: } catch (Throwable t) {
0695: return null;
0696: }
0697: }
0698:
0699: /**
0700: * Returns the distance from the baseline to the top of the text's bounding
0701: * box. Unlike the usual ascent, which is independent of the actual text.
0702: * Note that "True ascent" is not a standard term.
0703: */
0704: public static double trueAscent(TextLayout layout) {
0705: return -layout.getBounds().getY();
0706: }
0707:
0708: public static ImageIcon resize(ImageIcon icon, int extent) {
0709: return new ImageIcon(icon.getImage().getScaledInstance(extent,
0710: extent, Image.SCALE_SMOOTH));
0711: }
0712:
0713: /**
0714: * Resizes icon to 16 x 16.
0715: */
0716: public static ImageIcon toSmallIcon(ImageIcon icon) {
0717: return resize(icon, 16);
0718: }
0719:
0720: public static int swingThreadPriority() {
0721: final Int i = new Int();
0722:
0723: try {
0724: invokeOnEventThread(new Runnable() {
0725:
0726: public void run() {
0727: i.i = Thread.currentThread().getPriority();
0728: }
0729: });
0730: } catch (InvocationTargetException e) {
0731: Assert.shouldNeverReachHere();
0732: } catch (InterruptedException e) {
0733: Assert.shouldNeverReachHere();
0734: }
0735:
0736: return i.i;
0737: }
0738:
0739: /**
0740: * Fix for Sun Java Bug 4398733: if you click in an inactive JInternalFrame,
0741: * the mousePressed and mouseReleased events will be fired, but not the
0742: * mouseClicked event.
0743: */
0744: public static void fixClicks(final Component c) {
0745: //This is a time bomb because when (if?) Sun fixes the bug, this
0746: // method will
0747: //add an extra click. We should put an if statement here that
0748: // immediately
0749: //returns if the Java version is greater than or equal to that in
0750: // which the bug
0751: //is fixed. Problem is, we don't know what that version will be. [Jon
0752: // Aquino]
0753: c.addMouseListener(new MouseListener() {
0754:
0755: public void mousePressed(MouseEvent e) {
0756: add(e);
0757: }
0758:
0759: public void mouseExited(MouseEvent e) {
0760: add(e);
0761: }
0762:
0763: public void mouseClicked(MouseEvent e) {
0764: add(e);
0765: }
0766:
0767: public void mouseEntered(MouseEvent e) {
0768: add(e);
0769: }
0770:
0771: private MouseEvent event(int i) {
0772: return (MouseEvent) events.get(i);
0773: }
0774:
0775: public void mouseReleased(MouseEvent e) {
0776: add(e);
0777:
0778: if ((events.size() == 4)
0779: && (event(0).getID() == MouseEvent.MOUSE_PRESSED)
0780: && (event(1).getID() == MouseEvent.MOUSE_EXITED)
0781: && (event(2).getID() == MouseEvent.MOUSE_ENTERED)) {
0782: c.dispatchEvent(new MouseEvent(c,
0783: MouseEvent.MOUSE_CLICKED, System
0784: .currentTimeMillis(), e
0785: .getModifiers(), e.getX(),
0786: e.getY(), e.getClickCount(), e
0787: .isPopupTrigger()));
0788: }
0789: }
0790:
0791: private void add(MouseEvent e) {
0792: if (events.size() == 4) {
0793: events.remove(0);
0794: }
0795:
0796: events.add(e);
0797: }
0798:
0799: private ArrayList events = new ArrayList();
0800: });
0801: }
0802:
0803: /**
0804: * Listens to all internal frames (current and future) in a JDesktopPane.
0805: */
0806: public static void addInternalFrameListener(JDesktopPane pane,
0807: final InternalFrameListener listener) {
0808: JInternalFrame[] frames = pane.getAllFrames();
0809:
0810: for (int i = 0; i < frames.length; i++) {
0811: frames[i].addInternalFrameListener(listener);
0812: }
0813:
0814: pane.addContainerListener(new ContainerAdapter() {
0815:
0816: public void componentAdded(ContainerEvent e) {
0817: if (e.getChild() instanceof JInternalFrame) {
0818: ((JInternalFrame) e.getChild())
0819: .removeInternalFrameListener(listener);
0820: ((JInternalFrame) e.getChild())
0821: .addInternalFrameListener(listener);
0822: }
0823: }
0824: });
0825: }
0826:
0827: public static DocumentListener toDocumentListener(
0828: final ActionListener listener) {
0829: return new DocumentListener() {
0830:
0831: public void insertUpdate(DocumentEvent e) {
0832: listener.actionPerformed(new ActionEvent(e, 0, e
0833: .toString()));
0834: }
0835:
0836: public void removeUpdate(DocumentEvent e) {
0837: listener.actionPerformed(new ActionEvent(e, 0, e
0838: .toString()));
0839: }
0840:
0841: public void changedUpdate(DocumentEvent e) {
0842: listener.actionPerformed(new ActionEvent(e, 0, e
0843: .toString()));
0844: }
0845: };
0846: }
0847:
0848: public static ListDataListener toListDataListener(
0849: final ActionListener listener) {
0850: return new ListDataListener() {
0851:
0852: public void intervalAdded(ListDataEvent e) {
0853: listener.actionPerformed(new ActionEvent(e.getSource(),
0854: 0, e.toString()));
0855: }
0856:
0857: public void intervalRemoved(ListDataEvent e) {
0858: listener.actionPerformed(new ActionEvent(e.getSource(),
0859: 0, e.toString()));
0860: }
0861:
0862: public void contentsChanged(ListDataEvent e) {
0863: listener.actionPerformed(null);
0864: }
0865: };
0866: }
0867:
0868: public static InternalFrameListener toInternalFrameListener(
0869: final ActionListener listener) {
0870: return new InternalFrameListener() {
0871:
0872: private void fireActionPerformed(InternalFrameEvent e) {
0873: listener.actionPerformed(new ActionEvent(e.getSource(),
0874: e.getID(), e.toString()));
0875: }
0876:
0877: public void internalFrameActivated(InternalFrameEvent e) {
0878: fireActionPerformed(e);
0879: }
0880:
0881: public void internalFrameClosed(InternalFrameEvent e) {
0882: fireActionPerformed(e);
0883: }
0884:
0885: public void internalFrameClosing(InternalFrameEvent e) {
0886: fireActionPerformed(e);
0887: }
0888:
0889: public void internalFrameDeactivated(InternalFrameEvent e) {
0890: fireActionPerformed(e);
0891: }
0892:
0893: public void internalFrameDeiconified(InternalFrameEvent e) {
0894: fireActionPerformed(e);
0895: }
0896:
0897: public void internalFrameIconified(InternalFrameEvent e) {
0898: fireActionPerformed(e);
0899: }
0900:
0901: public void internalFrameOpened(InternalFrameEvent e) {
0902: fireActionPerformed(e);
0903: }
0904: };
0905: }
0906:
0907: /**
0908: * Returns a Timer that fires once, after the delay. The delay can be
0909: * restarted by restarting the Timer.
0910: */
0911: public static Timer createRestartableSingleEventTimer(int delay,
0912: ActionListener listener) {
0913: Timer timer = new Timer(delay, listener);
0914: timer.setCoalesce(true);
0915: timer.setInitialDelay(delay);
0916: timer.setRepeats(false);
0917:
0918: return timer;
0919: }
0920:
0921: public static ValidatingTextField createSyncdTextField(JSlider s) {
0922: int columns = (int) Math.ceil(Math.log(s.getMaximum())
0923: / Math.log(10));
0924:
0925: return createSyncdTextField(s, columns);
0926: }
0927:
0928: public static ValidatingTextField createSyncdTextField(JSlider s,
0929: int columns) {
0930: ValidatingTextField t = new ValidatingTextField(s.getValue()
0931: + "", columns, SwingConstants.RIGHT,
0932: ValidatingTextField.INTEGER_VALIDATOR,
0933: new ValidatingTextField.CompositeCleaner(
0934: new ValidatingTextField.Cleaner[] {
0935: new ValidatingTextField.BlankCleaner(""
0936: + s.getMinimum()),
0937: new ValidatingTextField.MinIntCleaner(s
0938: .getMinimum()),
0939: new ValidatingTextField.MaxIntCleaner(s
0940: .getMaximum()) }));
0941: sync(s, t);
0942: syncEnabledStates(s, t);
0943:
0944: return t;
0945: }
0946:
0947: /**
0948: * @see #createSyncdTextField(JSlider s, int columns)
0949: */
0950: public static void sync(final JSlider s, final ValidatingTextField t) {
0951: t.setText("" + s.getValue());
0952:
0953: final Boolean[] changing = new Boolean[] { Boolean.FALSE };
0954: s.addChangeListener(new ChangeListener() {
0955:
0956: public void stateChanged(ChangeEvent e) {
0957: if (changing[0] == Boolean.TRUE) {
0958: return;
0959: }
0960:
0961: changing[0] = Boolean.TRUE;
0962:
0963: try {
0964: t.setText("" + s.getValue());
0965: } finally {
0966: changing[0] = Boolean.FALSE;
0967: }
0968: }
0969: });
0970: t.getDocument().addDocumentListener(new DocumentListener() {
0971:
0972: private void changed() {
0973: if (changing[0] == Boolean.TRUE) {
0974: return;
0975: }
0976:
0977: changing[0] = Boolean.TRUE;
0978:
0979: try {
0980: s.setValue(t.getInteger());
0981: } finally {
0982: changing[0] = Boolean.FALSE;
0983: }
0984: }
0985:
0986: public void changedUpdate(DocumentEvent e) {
0987: changed();
0988: }
0989:
0990: public void insertUpdate(DocumentEvent e) {
0991: changed();
0992: }
0993:
0994: public void removeUpdate(DocumentEvent e) {
0995: changed();
0996: }
0997: });
0998: }
0999:
1000: public static void syncEnabledStates(final JComponent c1,
1001: final JComponent c2) {
1002: c2.setEnabled(c1.isEnabled());
1003: c1.addPropertyChangeListener("enabled",
1004: new PropertyChangeListener() {
1005:
1006: public void propertyChange(PropertyChangeEvent evt) {
1007: if (c1.isEnabled() == c2.isEnabled()) {
1008: return;
1009: }
1010:
1011: c2.setEnabled(c1.isEnabled());
1012: }
1013: });
1014: c2.addPropertyChangeListener("enabled",
1015: new PropertyChangeListener() {
1016:
1017: public void propertyChange(PropertyChangeEvent evt) {
1018: if (c1.isEnabled() == c2.isEnabled()) {
1019: return;
1020: }
1021:
1022: c1.setEnabled(c2.isEnabled());
1023: }
1024: });
1025: }
1026:
1027: public static void sync(final JSlider s1, final JSlider s2) {
1028: s2.setValue(s1.getValue());
1029: Assert.isTrue(s1.getMinimum() == s2.getMinimum());
1030: Assert.isTrue(s1.getMaximum() == s2.getMaximum());
1031:
1032: final Boolean[] changing = new Boolean[] { Boolean.FALSE };
1033: s1.addChangeListener(new ChangeListener() {
1034:
1035: public void stateChanged(ChangeEvent e) {
1036: if (changing[0] == Boolean.TRUE) {
1037: return;
1038: }
1039:
1040: changing[0] = Boolean.TRUE;
1041:
1042: try {
1043: s2.setValue(s1.getValue());
1044: } finally {
1045: changing[0] = Boolean.FALSE;
1046: }
1047: }
1048: });
1049: s2.addChangeListener(new ChangeListener() {
1050:
1051: public void stateChanged(ChangeEvent e) {
1052: if (changing[0] == Boolean.TRUE) {
1053: return;
1054: }
1055:
1056: changing[0] = Boolean.TRUE;
1057:
1058: try {
1059: s1.setValue(s2.getValue());
1060: } finally {
1061: changing[0] = Boolean.FALSE;
1062: }
1063: }
1064: });
1065: }
1066:
1067: public static void sync(final JCheckBox c1, final JCheckBox c2) {
1068: c2.setSelected(c1.isSelected());
1069:
1070: final Boolean[] changing = new Boolean[] { Boolean.FALSE };
1071: c1.addActionListener(new ActionListener() {
1072:
1073: public void actionPerformed(ActionEvent e) {
1074: if (changing[0] == Boolean.TRUE) {
1075: return;
1076: }
1077:
1078: changing[0] = Boolean.TRUE;
1079:
1080: try {
1081: c2.setSelected(c1.isSelected());
1082: } finally {
1083: changing[0] = Boolean.FALSE;
1084: }
1085: }
1086: });
1087: c2.addActionListener(new ActionListener() {
1088:
1089: public void actionPerformed(ActionEvent e) {
1090: if (changing[0] == Boolean.TRUE) {
1091: return;
1092: }
1093:
1094: changing[0] = Boolean.TRUE;
1095:
1096: try {
1097: c1.setSelected(c2.isSelected());
1098: } finally {
1099: changing[0] = Boolean.FALSE;
1100: }
1101: }
1102: });
1103: }
1104:
1105: public static List items(JComboBox comboBox) {
1106: ArrayList items = new ArrayList();
1107:
1108: for (int i = 0; i < comboBox.getItemCount(); i++) {
1109: items.add(comboBox.getItemAt(i));
1110: }
1111:
1112: return items;
1113: }
1114:
1115: /**
1116: * Calls #doClick so that events are fired.
1117: */
1118: public static void setSelectedWithClick(JCheckBox checkBox,
1119: boolean selected) {
1120: checkBox.setSelected(!selected);
1121: checkBox.doClick();
1122: }
1123:
1124: public static void setLocation(Component componentToMove,
1125: Location location, Component other) {
1126: Point p = new Point((int) other.getLocationOnScreen().getX()
1127: + (location.fromRight ? (other.getWidth()
1128: - componentToMove.getWidth() - location.x)
1129: : location.x), (int) other
1130: .getLocationOnScreen().getY()
1131: + (location.fromBottom ? (other.getHeight()
1132: - componentToMove.getHeight() - location.y)
1133: : location.y));
1134: if (!(componentToMove instanceof Window)) {
1135: SwingUtilities.convertPointFromScreen(p, componentToMove
1136: .getParent());
1137: }
1138: componentToMove.setLocation(p);
1139: }
1140:
1141: /**
1142: * Highlights a given component with a given color. Great for GridBagLayout
1143: * debugging.
1144: *
1145: */
1146: public static void highlightForDebugging(JComponent component,
1147: Color color) {
1148: component.setBackground(color);
1149: component.setBorder(BorderFactory.createMatteBorder(10, 10, 10,
1150: 10, color));
1151: }
1152:
1153: public static Component topCard(Container c) {
1154: Assert.isTrue(c.getLayout() instanceof CardLayout);
1155:
1156: Component[] components = c.getComponents();
1157:
1158: for (int i = 0; i < components.length; i++) {
1159: if (components[i].isVisible()) {
1160: return components[i];
1161: }
1162: }
1163:
1164: Assert.shouldNeverReachHere();
1165:
1166: return null;
1167: }
1168:
1169: /**
1170: * Work around Java Bug 4437688 "JFileChooser.getSelectedFile() returns
1171: * nothing when a file is selected" [Jon Aquino]
1172: */
1173: public static File[] selectedFiles(JFileChooser chooser) {
1174: return ((chooser.getSelectedFiles().length == 0) && (chooser
1175: .getSelectedFile() != null)) ? new File[] { chooser
1176: .getSelectedFile() } : chooser.getSelectedFiles();
1177: }
1178:
1179: public static ImageIcon toDisabledIcon(ImageIcon icon) {
1180: return new ImageIcon(GrayFilter.createDisabledImage((icon)
1181: .getImage()));
1182: }
1183:
1184: public static Component getDescendantOfClass(Class c,
1185: Container container) {
1186: for (int i = 0; i < container.getComponentCount(); i++) {
1187: if (c.isInstance(container.getComponent(i))) {
1188: return container.getComponent(i);
1189: }
1190:
1191: if (container.getComponent(i) instanceof Container) {
1192: Component descendant = getDescendantOfClass(c,
1193: (Container) container.getComponent(i));
1194:
1195: if (descendant != null) {
1196: return descendant;
1197: }
1198: }
1199: }
1200:
1201: return null;
1202: }
1203:
1204: /**
1205: * Ensures that the next frame is activated when #dispose is called
1206: * explicitly, in JDK 1.4. JDK 1.3 didn't have this problem.
1207: */
1208: public static void dispose(final JInternalFrame internalFrame,
1209: JDesktopPane desktopPane) {
1210: desktopPane.getDesktopManager().closeFrame(internalFrame);
1211: internalFrame.dispose();
1212: }
1213:
1214: private static class Int {
1215:
1216: public volatile int i;
1217: }
1218:
1219: public static class Location {
1220:
1221: private int x;
1222:
1223: private int y;
1224:
1225: private boolean fromRight;
1226:
1227: private boolean fromBottom;
1228:
1229: /**
1230: * Constructor taking an initial location, offset hint.
1231: *
1232: * @param fromBottom
1233: * whether y is the number of pixels between the bottom edges
1234: * of the toolbox and desktop pane, or between the top edges.
1235: */
1236: public Location(int x, boolean fromRight, int y,
1237: boolean fromBottom) {
1238: this .x = x;
1239: this .y = y;
1240: this .fromRight = fromRight;
1241: this .fromBottom = fromBottom;
1242: }
1243: }
1244:
1245: public static Cursor createCursorFromIcon(Image iconImage) {
1246: //Don't use GUIUtil#resize, which uses SCALE_SMOOTH, which
1247: //makes the check-mark icons chunky-looking.
1248: //[2004-02-27]
1249: ImageIcon icon = new ImageIcon(iconImage.getScaledInstance(12,
1250: 12, Image.SCALE_REPLICATE));
1251: ImageIcon basicCursor = IconLoader.icon("basic-cursor.png");
1252: BufferedImage image = new BufferedImage(basicCursor
1253: .getIconWidth(), basicCursor.getIconHeight(),
1254: BufferedImage.TYPE_INT_ARGB);
1255: Graphics2D graphics = (Graphics2D) image.getGraphics();
1256: graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
1257: RenderingHints.VALUE_ANTIALIAS_ON);
1258: graphics.drawImage(basicCursor.getImage(), 0, 0, null);
1259: graphics.drawImage(icon.getImage(), 10, 10, null);
1260: return createCursor(image, new Point(0, 15));
1261: }
1262:
1263: public static Cursor createCursor(Image image, Point hotSpot) {
1264: if (null == image) {
1265: return Cursor.getDefaultCursor();
1266: }
1267:
1268: if (Toolkit.getDefaultToolkit().getBestCursorSize(32, 32)
1269: .equals(new Dimension(0, 0))) {
1270: return Cursor.getDefaultCursor();
1271: }
1272:
1273: return Toolkit.getDefaultToolkit().createCustomCursor(image,
1274: hotSpot,
1275: I18N.get("ui.GUIUtil.jump-workbench-custom-cursor"));
1276: }
1277:
1278: /**
1279: * Based on Green, Roedy. "Java Glossary : focus".
1280: * Available from http://mindprod.com/jgloss/focus.html.
1281: * Internet; accessed 8 March 2004.
1282: */
1283: public static JTextArea makeTabMoveFocus(JTextArea textArea) {
1284: textArea.setFocusTraversalKeys(
1285: KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
1286: new HashSet(Arrays.asList(new Object[] { KeyStroke
1287: .getKeyStroke("TAB") })));
1288: textArea.setFocusTraversalKeys(
1289: KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
1290: new HashSet(Arrays.asList(new Object[] { KeyStroke
1291: .getKeyStroke("shift TAB") })));
1292: return textArea;
1293: }
1294:
1295: public static void shrinkFont(JComponent component) {
1296: component.setFont(component.getFont().deriveFont(
1297: (float) component.getFont().getSize() - 2));
1298: }
1299: }
|