Statistics
| Revision:

svn-gvsig-desktop / trunk / applications / appgvSIG / src / com / iver / cit / gvsig / gui / GUIUtil.java @ 312

History | View | Annotate | Download (36.2 KB)

1
/*
2
 * The Unified Mapping Platform (JUMP) is an extensible, interactive GUI
3
 * for visualizing and manipulating spatial features with geometry and attributes.
4
 *
5
 * Copyright (C) 2003 Vivid Solutions
6
 *
7
 * This program is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU General Public License
9
 * as published by the Free Software Foundation; either version 2
10
 * of the License, or (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20
 *
21
 * For more information, contact:
22
 *
23
 * Vivid Solutions
24
 * Suite #1A
25
 * 2328 Government Street
26
 * Victoria BC  V8T 5G5
27
 * Canada
28
 *
29
 * (250)385-6040
30
 * www.vividsolutions.com
31
 */
32
package com.iver.cit.gvsig.gui;
33

    
34
import java.awt.CardLayout;
35
import java.awt.Color;
36
import java.awt.Component;
37
import java.awt.Container;
38
import java.awt.Dimension;
39
import java.awt.Image;
40
import java.awt.Point;
41
import java.awt.Toolkit;
42
import java.awt.datatransfer.Clipboard;
43
import java.awt.datatransfer.Transferable;
44
import java.awt.event.ActionEvent;
45
import java.awt.event.ActionListener;
46
import java.awt.event.ContainerAdapter;
47
import java.awt.event.ContainerEvent;
48
import java.awt.event.MouseEvent;
49
import java.awt.event.MouseListener;
50
import java.awt.font.TextLayout;
51
import java.awt.geom.Point2D;
52
import java.beans.PropertyChangeEvent;
53
import java.beans.PropertyChangeListener;
54
import java.io.File;
55
import java.lang.reflect.InvocationTargetException;
56
import java.util.ArrayList;
57
import java.util.List;
58

    
59
import javax.swing.BorderFactory;
60
import javax.swing.GrayFilter;
61
import javax.swing.ImageIcon;
62
import javax.swing.JCheckBox;
63
import javax.swing.JComboBox;
64
import javax.swing.JComponent;
65
import javax.swing.JDesktopPane;
66
import javax.swing.JFileChooser;
67
import javax.swing.JInternalFrame;
68
import javax.swing.JOptionPane;
69
import javax.swing.JSlider;
70
import javax.swing.JTable;
71
import javax.swing.SwingConstants;
72
import javax.swing.SwingUtilities;
73
import javax.swing.Timer;
74
import javax.swing.UIManager;
75
import javax.swing.event.ChangeEvent;
76
import javax.swing.event.ChangeListener;
77
import javax.swing.event.DocumentEvent;
78
import javax.swing.event.DocumentListener;
79
import javax.swing.event.InternalFrameEvent;
80
import javax.swing.event.InternalFrameListener;
81
import javax.swing.event.ListDataEvent;
82
import javax.swing.event.ListDataListener;
83
import javax.swing.filechooser.FileFilter;
84
import javax.swing.plaf.basic.BasicComboBoxEditor;
85
import javax.swing.table.DefaultTableCellRenderer;
86
import javax.swing.table.TableColumn;
87

    
88
import com.vividsolutions.jts.util.Assert;
89
import com.vividsolutions.jump.util.StringUtil;
90

    
91

    
92
//<<TODO:NAMING>> Perhaps rename to WorkbenchUtilities and move to workbench package? [Jon Aquino]
93
public class GUIUtil {
94
    public final static String dbf = "dbf";
95
    public final static String dbfDesc = "DBF";
96
    public final static String fme = "fme";
97
    public final static String fmeDesc = "FME GML";
98
    public final static String gml = "gml";
99
    public final static String gmlDesc = "GML";
100

    
101
    //<<TODO:REFACTORING>> If these constants are only used by descendants of
102
    //AbstractDriver, they should be moved to AbstractDriver. GUIUtilities is
103
    //supposed to be very generic. [Jon Aquino]
104
    public final static String jml = "jml";
105
    public final static String jmlDesc = "JCS GML";
106
    public final static String shp = "shp";
107

    
108
    //<<TODO:NAMING>> "ESRI Shapefile" would be more precise. Is this what they
109
    //are? [Jon Aquino]
110
    public final static String shpDesc = "ESRI Shapefile";
111
    public final static String shx = "shx";
112
    public final static String shxDesc = "SHX";
113
    public final static String wkt = "wkt";
114
    public final static String wktDesc = "Well Known Text";
115
    public final static String wktaDesc = "Well Known Text (Show Attribute)";
116
    public final static String xml = "xml";
117
    public final static String xmlDesc = "XML";
118
    public static final FileFilter ALL_FILES_FILTER = new FileFilter() {
119
            public boolean accept(File f) {
120
                return true;
121
            }
122

    
123
            public String getDescription() {
124
                return "All Files";
125
            }
126
        };
127

    
128
    public GUIUtil() {
129
    }
130

    
131
    /**
132
     * Returns a string suitable for embeddind as HTML.  That is, all 
133
     * characters which have a special meaning in HTML are escaped
134
     * as character codes.
135
     * 
136
     * <p>
137
     * Based on code from Jason Sherman. See http://www.w3schools.com/html/html_asciiref.asp
138
     * </p>
139
     */
140
    public final static String escapeHTML(String value, boolean escapeSpaces,
141
        boolean escapeNewlines) {
142
        if (value == null) {
143
            return (null);
144
        }
145

    
146
        char[] content = new char[value.length()];
147
        value.getChars(0, value.length(), content, 0);
148

    
149
        StringBuffer result = new StringBuffer();
150

    
151
        for (int i = 0; i < content.length; i++) {
152
            switch (content[i]) {
153
            case ' ':
154
                result.append(escapeSpaces ? "&#32;" : " ");
155

    
156
                break;
157

    
158
            //Added \n [Jon Aquino]
159
            case '\n':
160
                result.append(escapeNewlines ? "<BR>" : "\n");
161

    
162
                break;
163

    
164
            case '!':
165
                result.append("&#33;");
166

    
167
                break;
168

    
169
            case '"':
170
                result.append("&#34;");
171

    
172
                break;
173

    
174
            case '#':
175
                result.append("&#35;");
176

    
177
                break;
178

    
179
            case '$':
180
                result.append("&#36;");
181

    
182
                break;
183

    
184
            case '%':
185
                result.append("&#37;");
186

    
187
                break;
188

    
189
            case '&':
190
                result.append("&#38;");
191

    
192
                break;
193

    
194
            case '\'':
195
                result.append("&#39;");
196

    
197
                break;
198

    
199
            case '(':
200
                result.append("&#40;");
201

    
202
                break;
203

    
204
            case ')':
205
                result.append("&#41;");
206

    
207
                break;
208

    
209
            case '*':
210
                result.append("&#42;");
211

    
212
                break;
213

    
214
            case '+':
215
                result.append("&#43;");
216

    
217
                break;
218

    
219
            case ',':
220
                result.append("&#44;");
221

    
222
                break;
223

    
224
            case '-':
225
                result.append("&#45;");
226

    
227
                break;
228

    
229
            case '.':
230
                result.append("&#46;");
231

    
232
                break;
233

    
234
            case '/':
235
                result.append("&#47;");
236

    
237
                break;
238

    
239
            case ':':
240
                result.append("&#58;");
241

    
242
                break;
243

    
244
            case ';':
245
                result.append("&#59;");
246

    
247
                break;
248

    
249
            case '<':
250
                result.append("&#60;");
251

    
252
                break;
253

    
254
            case '=':
255
                result.append("&#61;");
256

    
257
                break;
258

    
259
            case '>':
260
                result.append("&#62;");
261

    
262
                break;
263

    
264
            case '?':
265
                result.append("&#63;");
266

    
267
                break;
268

    
269
            case '@':
270
                result.append("&#64;");
271

    
272
                break;
273

    
274
            case '[':
275
                result.append("&#91;");
276

    
277
                break;
278

    
279
            case '\\':
280
                result.append("&#92;");
281

    
282
                break;
283

    
284
            case ']':
285
                result.append("&#93;");
286

    
287
                break;
288

    
289
            case '^':
290
                result.append("&#94;");
291

    
292
                break;
293

    
294
            case '_':
295
                result.append("&#95;");
296

    
297
                break;
298

    
299
            case '`':
300
                result.append("&#96;");
301

    
302
                break;
303

    
304
            case '{':
305
                result.append("&#123;");
306

    
307
                break;
308

    
309
            case '|':
310
                result.append("&#124;");
311

    
312
                break;
313

    
314
            case '}':
315
                result.append("&#125;");
316

    
317
                break;
318

    
319
            case '~':
320
                result.append("&#126;");
321

    
322
                break;
323

    
324
            default:
325
                result.append(content[i]);
326
            }
327
        }
328

    
329
        return (result.toString());
330
    }
331

    
332
    /*
333
     *  Get the extension of a file e.g. txt
334
     */
335
    public static String getExtension(File f) {
336
        String ext = "";
337
        String s = f.getName();
338
        int i = s.lastIndexOf('.');
339

    
340
        if ((i > 0) && (i < (s.length() - 1))) {
341
            ext = s.substring(i + 1).toLowerCase();
342
        }
343

    
344
        return ext;
345
    }
346

    
347
    public static Color alphaColor(Color color, int alpha) {
348
        return new Color(color.getRed(), color.getGreen(), color.getBlue(),
349
            alpha);
350
    }
351

    
352
    /**
353
     *  Centres the first component on the second
354
     *
355
     *@param  componentToMove      Description of the Parameter
356
     *@param  componentToCentreOn  Description of the Parameter
357
     */
358
    public static void centre(Component componentToMove,
359
        Component componentToCentreOn) {
360
        Dimension componentToCentreOnSize = componentToCentreOn.getSize();
361
        componentToMove.setLocation(componentToCentreOn.getX() +
362
            ((componentToCentreOnSize.width - componentToMove.getWidth()) / 2),
363
            componentToCentreOn.getY() +
364
            ((componentToCentreOnSize.height - componentToMove.getHeight()) / 2));
365
    }
366

    
367
    /**
368
     *  Centres the component on the screen
369
     *
370
     *@param  componentToMove  Description of the Parameter
371
     */
372
    public static void centreOnScreen(Component componentToMove) {
373
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
374
        componentToMove.setLocation((screenSize.width -
375
            componentToMove.getWidth()) / 2,
376
            (screenSize.height - componentToMove.getHeight()) / 2);
377
    }
378

    
379
    /**
380
     *  Centres the component on its window
381
     *
382
     *@param  componentToMove  Description of the Parameter
383
     */
384
    public static void centreOnWindow(Component componentToMove) {
385
        centre(componentToMove,
386
            SwingUtilities.windowForComponent(componentToMove));
387
    }
388

    
389
    /**
390
     *  Sets the column widths based on the first row.
391
     *
392
     *@param  table  Description of the Parameter
393
     */
394
    public static void chooseGoodColumnWidths(JTable table) {
395
        //Without padding, columns are slightly narrow, and we get "...". [Jon Aquino]
396
        final int PADDING = 5;
397

    
398
        if (table.getModel().getRowCount() == 0) {
399
            return;
400
        }
401

    
402
        for (int i = 0; i < table.getModel().getColumnCount(); i++) {
403
            TableColumn column = table.getColumnModel().getColumn(i);
404
            double headerWidth = table.getTableHeader().getDefaultRenderer()
405
                                      .getTableCellRendererComponent(table,
406
                    table.getModel().getColumnName(i), false, false, 0, i)
407
                                      .getPreferredSize().getWidth() + PADDING;
408
            double valueWidth = 10; // default in case of error
409

    
410
            try {
411
                valueWidth = table.getCellRenderer(0, i)
412
                                  .getTableCellRendererComponent(table,
413
                        table.getModel().getValueAt(0, i), false, false, 0, i)
414
                                  .getPreferredSize().getWidth() + PADDING;
415
            } catch (Exception ex) {
416
                // ignore the exception, since we can easily choose a default width
417
            }
418

    
419
            //Limit column width to 200 pixels.
420
            int width = Math.min(200,
421
                    Math.max((int) headerWidth, (int) valueWidth));
422
            column.setPreferredWidth(width);
423

    
424
            //Need to set the actual width too, otherwise actual width may end
425
            //up a bit less than the preferred width. [Jon Aquino]
426
            column.setWidth(width);
427
        }
428
    }
429

    
430
    public static JFileChooser createJFileChooserWithExistenceChecking() {
431
        return new JFileChooser() {
432
                public void approveSelection() {
433
                    File[] files = selectedFiles(this);
434

    
435
                    if (files.length == 0) {
436
                        return;
437
                    }
438

    
439
                    for (int i = 0; i < files.length; i++) {
440
                        if (!files[i].exists() && !files[i].isFile()) {
441
                            return;
442
                        }
443
                    }
444

    
445
                    super.approveSelection();
446
                }
447
            };
448
    }
449

    
450
    public static JFileChooser createJFileChooserWithOverwritePrompting() {
451
        return new JFileChooser() {
452
                public void approveSelection() {
453
                    if (selectedFiles(this).length != 1) {
454
                        return;
455
                    }
456

    
457
                    File selectedFile = selectedFiles(this)[0];
458

    
459
                    if (selectedFile.exists() && !selectedFile.isFile()) {
460
                        return;
461
                    }
462

    
463
                    if (selectedFile.exists()) {
464
                        int response = JOptionPane.showConfirmDialog(this,
465
                                "The file " + selectedFile.getName() +
466
                                " already exists. Do you " +
467
                                "want to replace the existing file?", "JUMP",
468
                                JOptionPane.YES_NO_OPTION);
469

    
470
                        if (response != JOptionPane.YES_OPTION) {
471
                            return;
472
                        }
473
                    }
474

    
475
                    super.approveSelection();
476
                }
477
            };
478
    }
479

    
480
    public static void doNotRoundDoubles(JTable table) {
481
        table.setDefaultRenderer(Double.class,
482
            new DefaultTableCellRenderer() {
483
                public void setValue(Object value) {
484
                    setText((value == null) ? "" : ("" + value));
485
                }
486

    
487
                {
488
                    setHorizontalAlignment(SwingConstants.RIGHT);
489
                }
490
            });
491
    }
492

    
493
    /**
494
     *  Workaround for Java Bug 4648654 "REGRESSION: Editable JComboBox focus
495
     *  misbehaves under Windows look and feel, proposed by Kleopatra
496
     *  (fastegal@addcom.de). Also see Java Bug 4673880 "REGRESSION: Modified
497
     *  editable JComboBox in Windows LAF does not release focus." This bug
498
     *  started occurring in Java 1.4.0.
499
     *
500
     *@param  cb  Description of the Parameter
501
     */
502
    public static void fixEditableComboBox(JComboBox cb) {
503
        Assert.isTrue(cb.isEditable());
504

    
505
        if (!UIManager.getLookAndFeel().getName().equals("Windows")) {
506
            return;
507
        }
508

    
509
        cb.setEditor(new BasicComboBoxEditor() {
510
                public void setItem(Object item) {
511
                    super.setItem(item);
512
                    editor.selectAll();
513
                }
514
            });
515
    }
516

    
517
    public static void handleThrowable(final Throwable t, final Component parent) {
518
        try {
519
            //<<TODO:UI>> A humane interface does not pop up an error dialog, as that interrupts
520
            //the user's work. Rather, error messages are displayed modelessly. See the book
521
            //"Humane Interfaces" (Raskin 2000) [Jon Aquino]
522
            SwingUtilities.invokeLater(new Runnable() {
523
                    public void run() {
524
                        t.printStackTrace(System.out);
525
                        JOptionPane.showMessageDialog(parent,
526
                            StringUtil.split(t.toString(), 80), "Exception",
527
                            JOptionPane.ERROR_MESSAGE);
528
                    }
529
                });
530
        } catch (Throwable t2) {
531
            t2.printStackTrace(System.out);
532
        }
533
    }
534

    
535
    /**
536
     * GUI operations should be performed only on the AWT event dispatching
537
     * thread. Blocks until the Runnable is finished.
538
     */
539
    public static void invokeOnEventThread(Runnable r)
540
        throws InterruptedException, InvocationTargetException {
541
        if (SwingUtilities.isEventDispatchThread()) {
542
            r.run();
543
        } else {
544
            SwingUtilities.invokeAndWait(r);
545
        }
546
    }
547

    
548
    public static String nameWithoutExtension(File file) {
549
        String name = file.getName();
550
        int dotPosition = name.indexOf('.');
551

    
552
        return (dotPosition < 0) ? name : name.substring(0, dotPosition);
553
    }
554

    
555
    public static void removeChoosableFileFilters(JFileChooser fc) {
556
        FileFilter[] filters = fc.getChoosableFileFilters();
557

    
558
        for (int i = 0; i < filters.length; i++) {
559
            fc.removeChoosableFileFilter(filters[i]);
560
        }
561

    
562
        return;
563
    }
564

    
565
    /**
566
     * @param extensions e.g. txt
567
     */
568
    public static FileFilter createFileFilter(final String description,
569
        final String[] extensions) {
570
        return new FileFilter() {
571
                public boolean accept(File f) {
572
                    if (f.isDirectory()) {
573
                        return true;
574
                    }
575

    
576
                    for (int i = 0; i < extensions.length; i++) {
577
                        if (GUIUtil.getExtension(f).equalsIgnoreCase(extensions[i])) {
578
                            return true;
579
                        }
580
                    }
581

    
582
                    return false;
583
                }
584

    
585
                public String getDescription() {
586
                    ArrayList extensionStrings = new ArrayList();
587

    
588
                    for (int i = 0; i < extensions.length; i++) {
589
                        extensionStrings.add("*." + extensions[i]);
590
                    }
591

    
592
                    return description + " (" +
593
                    StringUtil.replaceAll(StringUtil.toCommaDelimitedString(
594
                            extensionStrings), ",", ";") + ")";
595
                }
596
            };
597
    }
598

    
599
    /**
600
     *@param  color  a Color with possibly an alpha less than 255
601
     *@return        a Color with alpha equal to 255, but equivalent to the
602
     *      original translucent colour on a white background
603
     */
604
    public static Color toSimulatedTransparency(Color color) {
605
        //My guess, but it seems to work! [Jon Aquino]
606
        return new Color(color.getRed() +
607
            (int) (((255 - color.getRed()) * (255 - color.getAlpha())) / 255d),
608
            color.getGreen() +
609
            (int) (((255 - color.getGreen()) * (255 - color.getAlpha())) / 255d),
610
            color.getBlue() +
611
            (int) (((255 - color.getBlue()) * (255 - color.getAlpha())) / 255d));
612
    }
613

    
614
    public static String truncateString(String s, int maxLength) {
615
        if (s.length() < maxLength) {
616
            return s;
617
        }
618

    
619
        return s.substring(0, maxLength - 3) + "...";
620
    }
621

    
622
    public static Point2D subtract(Point2D a, Point2D b) {
623
        return new Point2D.Double(a.getX() - b.getX(), a.getY() - b.getY());
624
    }
625

    
626
    public static Point2D add(Point2D a, Point2D b) {
627
        return new Point2D.Double(a.getX() + b.getX(), a.getY() + b.getY());
628
    }
629

    
630
    public static Point2D multiply(Point2D v, double x) {
631
        return new Point2D.Double(v.getX() * x, v.getY() * x);
632
    }
633

    
634
    /**
635
     * The JVM's clipboard implementation is buggy (see bugs 4644554 and 4522198
636
     * in Sun's Java bug database). This method is a workaround that returns null
637
     * if an exception is thrown, as suggested in the bug reports.
638
     */
639
    public static Transferable getContents(Clipboard clipboard) {
640
        try {
641
            return clipboard.getContents(null);
642
        } catch (Throwable t) {
643
            return null;
644
        }
645
    }
646

    
647
    /**
648
     * Returns the distance from the baseline to the top of the text's bounding box.
649
     * Unlike the usual ascent, which is independent of the actual text.
650
     * Note that "True ascent" is not a standard term.
651
     */
652
    public static double trueAscent(TextLayout layout) {
653
        return -layout.getBounds().getY();
654
    }
655

    
656
    public static ImageIcon resize(ImageIcon icon, int extent) {
657
        return new ImageIcon(icon.getImage().getScaledInstance(extent, extent,
658
                Image.SCALE_SMOOTH));
659
    }
660

    
661
    /**
662
     * Resizes icon to 16 x 16.
663
     */
664
    public static ImageIcon toSmallIcon(ImageIcon icon) {
665
        return resize(icon, 16);
666
    }
667

    
668
    public static int swingThreadPriority() {
669
        final Int i = new Int();
670

    
671
        try {
672
            invokeOnEventThread(new Runnable() {
673
                    public void run() {
674
                        i.i = Thread.currentThread().getPriority();
675
                    }
676
                });
677
        } catch (InvocationTargetException e) {
678
            Assert.shouldNeverReachHere();
679
        } catch (InterruptedException e) {
680
            Assert.shouldNeverReachHere();
681
        }
682

    
683
        return i.i;
684
    }
685

    
686
    /**
687
     * Fix for Sun Java Bug 4398733: if you click in an inactive JInternalFrame,
688
     * the mousePressed and mouseReleased events will be fired, but not the
689
     * mouseClicked event.
690
     */
691
    public static void fixClicks(final Component c) {
692
        //This is a time bomb because when (if?) Sun fixes the bug, this method will
693
        //add an extra click. We should put an if statement here that immediately
694
        //returns if the Java version is greater than or equal to that in which the bug
695
        //is fixed. Problem is, we don't know what that version will be. [Jon Aquino]
696
        c.addMouseListener(new MouseListener() {
697
                public void mousePressed(MouseEvent e) {
698
                    add(e);
699
                }
700

    
701
                public void mouseExited(MouseEvent e) {
702
                    add(e);
703
                }
704

    
705
                public void mouseClicked(MouseEvent e) {
706
                    add(e);
707
                }
708

    
709
                public void mouseEntered(MouseEvent e) {
710
                    add(e);
711
                }
712

    
713
                private MouseEvent event(int i) {
714
                    return (MouseEvent) events.get(i);
715
                }
716

    
717
                public void mouseReleased(MouseEvent e) {
718
                    add(e);
719

    
720
                    if ((events.size() == 4) &&
721
                            (event(0).getID() == MouseEvent.MOUSE_PRESSED) &&
722
                            (event(1).getID() == MouseEvent.MOUSE_EXITED) &&
723
                            (event(2).getID() == MouseEvent.MOUSE_ENTERED)) {
724
                        c.dispatchEvent(new MouseEvent(c,
725
                                MouseEvent.MOUSE_CLICKED,
726
                                System.currentTimeMillis(), e.getModifiers(),
727
                                e.getX(), e.getY(), e.getClickCount(),
728
                                e.isPopupTrigger()));
729
                    }
730
                }
731

    
732
                private void add(MouseEvent e) {
733
                    if (events.size() == 4) {
734
                        events.remove(0);
735
                    }
736

    
737
                    events.add(e);
738
                }
739

    
740
                private ArrayList events = new ArrayList();
741
            });
742
    }
743

    
744
    /**
745
     * Listens to all internal frames (current and future) in a JDesktopPane.
746
     */
747
    public static void addInternalFrameListener(JDesktopPane pane,
748
        final InternalFrameListener listener) {
749
        JInternalFrame[] frames = pane.getAllFrames();
750

    
751
        for (int i = 0; i < frames.length; i++) {
752
            frames[i].addInternalFrameListener(listener);
753
        }
754

    
755
        pane.addContainerListener(new ContainerAdapter() {
756
                public void componentAdded(ContainerEvent e) {
757
                    if (e.getChild() instanceof JInternalFrame) {
758
                        ((JInternalFrame) e.getChild()).removeInternalFrameListener(listener);
759
                        ((JInternalFrame) e.getChild()).addInternalFrameListener(listener);
760
                    }
761
                }
762
            });
763
    }
764

    
765
    public static DocumentListener toDocumentListener(
766
        final ActionListener listener) {
767
        return new DocumentListener() {
768
                public void insertUpdate(DocumentEvent e) {
769
                    listener.actionPerformed(new ActionEvent(e, 0, e.toString()));
770
                }
771

    
772
                public void removeUpdate(DocumentEvent e) {
773
                    listener.actionPerformed(new ActionEvent(e, 0, e.toString()));
774
                }
775

    
776
                public void changedUpdate(DocumentEvent e) {
777
                    listener.actionPerformed(new ActionEvent(e, 0, e.toString()));
778
                }
779
            };
780
    }
781

    
782
    public static ListDataListener toListDataListener(
783
        final ActionListener listener) {
784
        return new ListDataListener() {
785
                public void intervalAdded(ListDataEvent e) {
786
                    listener.actionPerformed(new ActionEvent(e.getSource(), 0,
787
                            e.toString()));
788
                }
789

    
790
                public void intervalRemoved(ListDataEvent e) {
791
                    listener.actionPerformed(new ActionEvent(e.getSource(), 0,
792
                            e.toString()));
793
                }
794

    
795
                public void contentsChanged(ListDataEvent e) {
796
                    listener.actionPerformed(null);
797
                }
798
            };
799
    }
800

    
801
    public static InternalFrameListener toInternalFrameListener(
802
        final ActionListener listener) {
803
        return new InternalFrameListener() {
804
                private void fireActionPerformed(InternalFrameEvent e) {
805
                    listener.actionPerformed(new ActionEvent(e.getSource(),
806
                            e.getID(), e.toString()));
807
                }
808

    
809
                public void internalFrameActivated(InternalFrameEvent e) {
810
                    fireActionPerformed(e);
811
                }
812

    
813
                public void internalFrameClosed(InternalFrameEvent e) {
814
                    fireActionPerformed(e);
815
                }
816

    
817
                public void internalFrameClosing(InternalFrameEvent e) {
818
                    fireActionPerformed(e);
819
                }
820

    
821
                public void internalFrameDeactivated(InternalFrameEvent e) {
822
                    fireActionPerformed(e);
823
                }
824

    
825
                public void internalFrameDeiconified(InternalFrameEvent e) {
826
                    fireActionPerformed(e);
827
                }
828

    
829
                public void internalFrameIconified(InternalFrameEvent e) {
830
                    fireActionPerformed(e);
831
                }
832

    
833
                public void internalFrameOpened(InternalFrameEvent e) {
834
                    fireActionPerformed(e);
835
                }
836
            };
837
    }
838

    
839
    /**
840
     * Returns a Timer that fires once, after the delay. The delay can be restarted
841
     * by restarting the Timer.
842
     */
843
    public static Timer createRestartableSingleEventTimer(int delay,
844
        ActionListener listener) {
845
        Timer timer = new Timer(delay, listener);
846
        timer.setCoalesce(true);
847
        timer.setInitialDelay(delay);
848
        timer.setRepeats(false);
849

    
850
        return timer;
851
    }
852

    
853
    public static ValidatingTextField createSyncdTextField(JSlider s) {
854
        int columns = (int) Math.ceil(Math.log(s.getMaximum()) / Math.log(10));
855

    
856
        return createSyncdTextField(s, columns);
857
    }
858

    
859
    public static ValidatingTextField createSyncdTextField(JSlider s,
860
        int columns) {
861
        ValidatingTextField t = new ValidatingTextField(s.getValue() + "",
862
                columns, SwingConstants.RIGHT,
863
                ValidatingTextField.INTEGER_VALIDATOR,
864
                new ValidatingTextField.CompositeCleaner(new ValidatingTextField.Cleaner[] {
865
                        new ValidatingTextField.BlankCleaner("" +
866
                            s.getMinimum()),
867
                        new ValidatingTextField.MinIntCleaner(s.getMinimum()),
868
                        new ValidatingTextField.MaxIntCleaner(s.getMaximum())
869
                    }));
870
        sync(s, t);
871
        syncEnabledStates(s, t);
872

    
873
        return t;
874
    }
875
    
876
    /**
877
     * @see #createSyncdTextField(JSlider s, int columns)
878
     */
879
     public static void sync(final JSlider s, final ValidatingTextField t) {
880
        t.setText("" + s.getValue());
881

    
882
        final Boolean[] changing = new Boolean[] { Boolean.FALSE };
883
        s.addChangeListener(new ChangeListener() {
884
                public void stateChanged(ChangeEvent e) {
885
                    if (changing[0] == Boolean.TRUE) {
886
                        return;
887
                    }
888

    
889
                    changing[0] = Boolean.TRUE;
890

    
891
                    try {
892
                        t.setText("" + s.getValue());
893
                    } finally {
894
                        changing[0] = Boolean.FALSE;
895
                    }
896
                }
897
            });
898
        t.getDocument().addDocumentListener(new DocumentListener() {
899
                private void changed() {
900
                    if (changing[0] == Boolean.TRUE) {
901
                        return;
902
                    }
903

    
904
                    changing[0] = Boolean.TRUE;
905

    
906
                    try {
907
                        s.setValue(t.getInteger());
908
                    } finally {
909
                        changing[0] = Boolean.FALSE;
910
                    }
911
                }
912

    
913
                public void changedUpdate(DocumentEvent e) {
914
                    changed();
915
                }
916

    
917
                public void insertUpdate(DocumentEvent e) {
918
                    changed();
919
                }
920

    
921
                public void removeUpdate(DocumentEvent e) {
922
                    changed();
923
                }
924
            });
925
    }
926

    
927
    public static void syncEnabledStates(final JComponent c1,
928
        final JComponent c2) {
929
        c2.setEnabled(c1.isEnabled());
930
        c1.addPropertyChangeListener("enabled",
931
            new PropertyChangeListener() {
932
                public void propertyChange(PropertyChangeEvent evt) {
933
                    if (c1.isEnabled() == c2.isEnabled()) {
934
                        return;
935
                    }
936

    
937
                    c2.setEnabled(c1.isEnabled());
938
                }
939
            });
940
        c2.addPropertyChangeListener("enabled",
941
            new PropertyChangeListener() {
942
                public void propertyChange(PropertyChangeEvent evt) {
943
                    if (c1.isEnabled() == c2.isEnabled()) {
944
                        return;
945
                    }
946

    
947
                    c1.setEnabled(c2.isEnabled());
948
                }
949
            });
950
    }
951

    
952
    public static void sync(final JSlider s1, final JSlider s2) {
953
        s2.setValue(s1.getValue());
954
        Assert.isTrue(s1.getMinimum() == s2.getMinimum());
955
        Assert.isTrue(s1.getMaximum() == s2.getMaximum());
956

    
957
        final Boolean[] changing = new Boolean[] { Boolean.FALSE };
958
        s1.addChangeListener(new ChangeListener() {
959
                public void stateChanged(ChangeEvent e) {
960
                    if (changing[0] == Boolean.TRUE) {
961
                        return;
962
                    }
963

    
964
                    changing[0] = Boolean.TRUE;
965

    
966
                    try {
967
                        s2.setValue(s1.getValue());
968
                    } finally {
969
                        changing[0] = Boolean.FALSE;
970
                    }
971
                }
972
            });
973
        s2.addChangeListener(new ChangeListener() {
974
                public void stateChanged(ChangeEvent e) {
975
                    if (changing[0] == Boolean.TRUE) {
976
                        return;
977
                    }
978

    
979
                    changing[0] = Boolean.TRUE;
980

    
981
                    try {
982
                        s1.setValue(s2.getValue());
983
                    } finally {
984
                        changing[0] = Boolean.FALSE;
985
                    }
986
                }
987
            });
988
    }
989

    
990
    public static void sync(final JCheckBox c1, final JCheckBox c2) {
991
        c2.setSelected(c1.isSelected());
992

    
993
        final Boolean[] changing = new Boolean[] { Boolean.FALSE };
994
        c1.addActionListener(new ActionListener() {
995
                public void actionPerformed(ActionEvent e) {
996
                    if (changing[0] == Boolean.TRUE) {
997
                        return;
998
                    }
999

    
1000
                    changing[0] = Boolean.TRUE;
1001

    
1002
                    try {
1003
                        c2.setSelected(c1.isSelected());
1004
                    } finally {
1005
                        changing[0] = Boolean.FALSE;
1006
                    }
1007
                }
1008
            });
1009
        c2.addActionListener(new ActionListener() {
1010
                public void actionPerformed(ActionEvent e) {
1011
                    if (changing[0] == Boolean.TRUE) {
1012
                        return;
1013
                    }
1014

    
1015
                    changing[0] = Boolean.TRUE;
1016

    
1017
                    try {
1018
                        c1.setSelected(c2.isSelected());
1019
                    } finally {
1020
                        changing[0] = Boolean.FALSE;
1021
                    }
1022
                }
1023
            });
1024
    }
1025

    
1026
    public static List items(JComboBox comboBox) {
1027
        ArrayList items = new ArrayList();
1028

    
1029
        for (int i = 0; i < comboBox.getItemCount(); i++) {
1030
            items.add(comboBox.getItemAt(i));
1031
        }
1032

    
1033
        return items;
1034
    }
1035

    
1036
    /**
1037
     * Calls #doClick so that events are fired.
1038
     */
1039
    public static void setSelectedWithClick(JCheckBox checkBox, boolean selected) {
1040
        checkBox.setSelected(!selected);
1041
        checkBox.doClick();
1042
    }
1043

    
1044
    public static void setLocation(Component componentToMove,
1045
        Location location, Component other) {
1046
        Point p = new Point((int) other.getLocationOnScreen().getX() +
1047
                (location.fromRight
1048
                ? (other.getWidth() - componentToMove.getWidth() - location.x)
1049
                : location.x),
1050
                (int) other.getLocationOnScreen().getY() +
1051
                (location.fromBottom
1052
                ? (other.getHeight() - componentToMove.getHeight() -
1053
                location.y) : location.y));
1054
        SwingUtilities.convertPointFromScreen(p, componentToMove.getParent());
1055
        componentToMove.setLocation(p);
1056
    }
1057

    
1058

    
1059
    /** Highlights a given component with a given color. 
1060
     * Great for GridBagLayout debugging. 
1061
     *
1062
     * @author Jon Aquino
1063
     */
1064
    public static void highlightForDebugging(JComponent component, Color color) {
1065
        component.setBackground(color);
1066
        component.setBorder(BorderFactory.createMatteBorder(10, 10, 10, 10,
1067
                color));
1068
    }
1069

    
1070
    public static Component topCard(Container c) {
1071
        Assert.isTrue(c.getLayout() instanceof CardLayout);
1072

    
1073
        Component[] components = c.getComponents();
1074

    
1075
        for (int i = 0; i < components.length; i++) {
1076
            if (components[i].isVisible()) {
1077
                return components[i];
1078
            }
1079
        }
1080

    
1081
        Assert.shouldNeverReachHere();
1082

    
1083
        return null;
1084
    }
1085

    
1086
    /**
1087
     * Work around Java Bug 4437688 "JFileChooser.getSelectedFile() returns
1088
     * nothing when a file is selected" [Jon Aquino]
1089
     */
1090
    public static File[] selectedFiles(JFileChooser chooser) {
1091
        return ((chooser.getSelectedFiles().length == 0) &&
1092
        (chooser.getSelectedFile() != null))
1093
        ? new File[] { chooser.getSelectedFile() } : chooser.getSelectedFiles();
1094
    }
1095

    
1096
    public static ImageIcon toDisabledIcon(ImageIcon icon) {
1097
        return new ImageIcon(GrayFilter.createDisabledImage((icon).getImage()));
1098
    }
1099

    
1100
    public static Component getDescendantOfClass(Class c, Container container) {
1101
        for (int i = 0; i < container.getComponentCount(); i++) {
1102
            if (c.isInstance(container.getComponent(i))) {
1103
                return container.getComponent(i);
1104
            }
1105

    
1106
            if (container.getComponent(i) instanceof Container) {
1107
                Component descendant = getDescendantOfClass(c,
1108
                        (Container) container.getComponent(i));
1109

    
1110
                if (descendant != null) {
1111
                    return descendant;
1112
                }
1113
            }
1114
        }
1115

    
1116
        return null;
1117
    }
1118

    
1119
    /**
1120
     * Ensures that the next frame is activated when #dispose is called
1121
     * explicitly, in JDK 1.4. JDK 1.3 didn't have this problem.
1122
     */
1123
    public static void dispose(final JInternalFrame internalFrame,
1124
        JDesktopPane desktopPane) {
1125
        desktopPane.getDesktopManager().closeFrame(internalFrame);
1126
        internalFrame.dispose();
1127
    }
1128

    
1129
    private static class Int {
1130
        public volatile int i;
1131
    }
1132

    
1133
    public static class Location {
1134
        private int x;
1135
        private int y;
1136
        private boolean fromRight;
1137
        private boolean fromBottom;
1138

    
1139
        /**
1140
         * Constructor taking an initial location, offset hint.
1141
         *
1142
         * @param fromBottom whether y is the number of pixels between the bottom
1143
         * edges of the toolbox and desktop pane, or between the top edges.
1144
         */
1145
        public Location(int x, boolean fromRight, int y, boolean fromBottom) {
1146
            this.x = x;
1147
            this.y = y;
1148
            this.fromRight = fromRight;
1149
            this.fromBottom = fromBottom;
1150
        }
1151
    }
1152
}