Statistics
| Revision:

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

History | View | Annotate | Download (37.4 KB)

1 312 fernando
/*
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 1103 fjp
/* gvSIG. Sistema de Informaci?n Geogr?fica de la Generalitat Valenciana
33
 *
34
 * Copyright (C) 2004 IVER T.I. and Generalitat Valenciana.
35
 *
36
 * This program is free software; you can redistribute it and/or
37
 * modify it under the terms of the GNU General Public License
38
 * as published by the Free Software Foundation; either version 2
39
 * of the License, or (at your option) any later version.
40
 *
41
 * This program is distributed in the hope that it will be useful,
42
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
43
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
44
 * GNU General Public License for more details.
45
 *
46
 * You should have received a copy of the GNU General Public License
47
 * along with this program; if not, write to the Free Software
48
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,USA.
49
 *
50
 * For more information, contact:
51
 *
52
 *  Generalitat Valenciana
53
 *   Conselleria d'Infraestructures i Transport
54
 *   Av. Blasco Ib??ez, 50
55
 *   46010 VALENCIA
56
 *   SPAIN
57
 *
58
 *      +34 963862235
59
 *   gvsig@gva.es
60
 *      www.gvsig.gva.es
61
 *
62
 *    or
63
 *
64
 *   IVER T.I. S.A
65
 *   Salamanca 50
66
 *   46005 Valencia
67
 *   Spain
68
 *
69
 *   +34 963163400
70
 *   dac@iver.es
71
 */
72 312 fernando
package com.iver.cit.gvsig.gui;
73
74
import java.awt.CardLayout;
75
import java.awt.Color;
76
import java.awt.Component;
77
import java.awt.Container;
78
import java.awt.Dimension;
79
import java.awt.Image;
80
import java.awt.Point;
81
import java.awt.Toolkit;
82
import java.awt.datatransfer.Clipboard;
83
import java.awt.datatransfer.Transferable;
84
import java.awt.event.ActionEvent;
85
import java.awt.event.ActionListener;
86
import java.awt.event.ContainerAdapter;
87
import java.awt.event.ContainerEvent;
88
import java.awt.event.MouseEvent;
89
import java.awt.event.MouseListener;
90
import java.awt.font.TextLayout;
91
import java.awt.geom.Point2D;
92
import java.beans.PropertyChangeEvent;
93
import java.beans.PropertyChangeListener;
94
import java.io.File;
95
import java.lang.reflect.InvocationTargetException;
96
import java.util.ArrayList;
97
import java.util.List;
98
99
import javax.swing.BorderFactory;
100
import javax.swing.GrayFilter;
101
import javax.swing.ImageIcon;
102
import javax.swing.JCheckBox;
103
import javax.swing.JComboBox;
104
import javax.swing.JComponent;
105
import javax.swing.JDesktopPane;
106
import javax.swing.JFileChooser;
107
import javax.swing.JInternalFrame;
108
import javax.swing.JOptionPane;
109
import javax.swing.JSlider;
110
import javax.swing.JTable;
111
import javax.swing.SwingConstants;
112
import javax.swing.SwingUtilities;
113
import javax.swing.Timer;
114
import javax.swing.UIManager;
115
import javax.swing.event.ChangeEvent;
116
import javax.swing.event.ChangeListener;
117
import javax.swing.event.DocumentEvent;
118
import javax.swing.event.DocumentListener;
119
import javax.swing.event.InternalFrameEvent;
120
import javax.swing.event.InternalFrameListener;
121
import javax.swing.event.ListDataEvent;
122
import javax.swing.event.ListDataListener;
123
import javax.swing.filechooser.FileFilter;
124
import javax.swing.plaf.basic.BasicComboBoxEditor;
125
import javax.swing.table.DefaultTableCellRenderer;
126
import javax.swing.table.TableColumn;
127
128 11734 jaume
import org.gvsig.gui.beans.swing.ValidatingTextField;
129
130 312 fernando
import com.vividsolutions.jts.util.Assert;
131
import com.vividsolutions.jump.util.StringUtil;
132
133
134
//<<TODO:NAMING>> Perhaps rename to WorkbenchUtilities and move to workbench package? [Jon Aquino]
135
public class GUIUtil {
136
    public final static String dbf = "dbf";
137
    public final static String dbfDesc = "DBF";
138
    public final static String fme = "fme";
139
    public final static String fmeDesc = "FME GML";
140
    public final static String gml = "gml";
141
    public final static String gmlDesc = "GML";
142
143
    //<<TODO:REFACTORING>> If these constants are only used by descendants of
144
    //AbstractDriver, they should be moved to AbstractDriver. GUIUtilities is
145
    //supposed to be very generic. [Jon Aquino]
146
    public final static String jml = "jml";
147
    public final static String jmlDesc = "JCS GML";
148
    public final static String shp = "shp";
149
150
    //<<TODO:NAMING>> "ESRI Shapefile" would be more precise. Is this what they
151
    //are? [Jon Aquino]
152
    public final static String shpDesc = "ESRI Shapefile";
153
    public final static String shx = "shx";
154
    public final static String shxDesc = "SHX";
155
    public final static String wkt = "wkt";
156
    public final static String wktDesc = "Well Known Text";
157
    public final static String wktaDesc = "Well Known Text (Show Attribute)";
158
    public final static String xml = "xml";
159
    public final static String xmlDesc = "XML";
160
    public static final FileFilter ALL_FILES_FILTER = new FileFilter() {
161
            public boolean accept(File f) {
162
                return true;
163
            }
164
165
            public String getDescription() {
166
                return "All Files";
167
            }
168
        };
169
170
    public GUIUtil() {
171
    }
172
173
    /**
174
     * Returns a string suitable for embeddind as HTML.  That is, all
175
     * characters which have a special meaning in HTML are escaped
176
     * as character codes.
177
     *
178
     * <p>
179
     * Based on code from Jason Sherman. See http://www.w3schools.com/html/html_asciiref.asp
180
     * </p>
181
     */
182
    public final static String escapeHTML(String value, boolean escapeSpaces,
183
        boolean escapeNewlines) {
184
        if (value == null) {
185
            return (null);
186
        }
187
188
        char[] content = new char[value.length()];
189
        value.getChars(0, value.length(), content, 0);
190
191
        StringBuffer result = new StringBuffer();
192
193
        for (int i = 0; i < content.length; i++) {
194
            switch (content[i]) {
195
            case ' ':
196
                result.append(escapeSpaces ? "&#32;" : " ");
197
198
                break;
199
200
            //Added \n [Jon Aquino]
201
            case '\n':
202
                result.append(escapeNewlines ? "<BR>" : "\n");
203
204
                break;
205
206
            case '!':
207
                result.append("&#33;");
208
209
                break;
210
211
            case '"':
212
                result.append("&#34;");
213
214
                break;
215
216
            case '#':
217
                result.append("&#35;");
218
219
                break;
220
221
            case '$':
222
                result.append("&#36;");
223
224
                break;
225
226
            case '%':
227
                result.append("&#37;");
228
229
                break;
230
231
            case '&':
232
                result.append("&#38;");
233
234
                break;
235
236
            case '\'':
237
                result.append("&#39;");
238
239
                break;
240
241
            case '(':
242
                result.append("&#40;");
243
244
                break;
245
246
            case ')':
247
                result.append("&#41;");
248
249
                break;
250
251
            case '*':
252
                result.append("&#42;");
253
254
                break;
255
256
            case '+':
257
                result.append("&#43;");
258
259
                break;
260
261
            case ',':
262
                result.append("&#44;");
263
264
                break;
265
266
            case '-':
267
                result.append("&#45;");
268
269
                break;
270
271
            case '.':
272
                result.append("&#46;");
273
274
                break;
275
276
            case '/':
277
                result.append("&#47;");
278
279
                break;
280
281
            case ':':
282
                result.append("&#58;");
283
284
                break;
285
286
            case ';':
287
                result.append("&#59;");
288
289
                break;
290
291
            case '<':
292
                result.append("&#60;");
293
294
                break;
295
296
            case '=':
297
                result.append("&#61;");
298
299
                break;
300
301
            case '>':
302
                result.append("&#62;");
303
304
                break;
305
306
            case '?':
307
                result.append("&#63;");
308
309
                break;
310
311
            case '@':
312
                result.append("&#64;");
313
314
                break;
315
316
            case '[':
317
                result.append("&#91;");
318
319
                break;
320
321
            case '\\':
322
                result.append("&#92;");
323
324
                break;
325
326
            case ']':
327
                result.append("&#93;");
328
329
                break;
330
331
            case '^':
332
                result.append("&#94;");
333
334
                break;
335
336
            case '_':
337
                result.append("&#95;");
338
339
                break;
340
341
            case '`':
342
                result.append("&#96;");
343
344
                break;
345
346
            case '{':
347
                result.append("&#123;");
348
349
                break;
350
351
            case '|':
352
                result.append("&#124;");
353
354
                break;
355
356
            case '}':
357
                result.append("&#125;");
358
359
                break;
360
361
            case '~':
362
                result.append("&#126;");
363
364
                break;
365
366
            default:
367
                result.append(content[i]);
368
            }
369
        }
370
371
        return (result.toString());
372
    }
373
374
    /*
375
     *  Get the extension of a file e.g. txt
376
     */
377
    public static String getExtension(File f) {
378
        String ext = "";
379
        String s = f.getName();
380
        int i = s.lastIndexOf('.');
381
382
        if ((i > 0) && (i < (s.length() - 1))) {
383
            ext = s.substring(i + 1).toLowerCase();
384
        }
385
386
        return ext;
387
    }
388
389
    public static Color alphaColor(Color color, int alpha) {
390
        return new Color(color.getRed(), color.getGreen(), color.getBlue(),
391
            alpha);
392
    }
393
394
    /**
395
     *  Centres the first component on the second
396
     *
397
     *@param  componentToMove      Description of the Parameter
398
     *@param  componentToCentreOn  Description of the Parameter
399
     */
400
    public static void centre(Component componentToMove,
401
        Component componentToCentreOn) {
402
        Dimension componentToCentreOnSize = componentToCentreOn.getSize();
403
        componentToMove.setLocation(componentToCentreOn.getX() +
404
            ((componentToCentreOnSize.width - componentToMove.getWidth()) / 2),
405
            componentToCentreOn.getY() +
406
            ((componentToCentreOnSize.height - componentToMove.getHeight()) / 2));
407
    }
408
409
    /**
410
     *  Centres the component on the screen
411
     *
412
     *@param  componentToMove  Description of the Parameter
413
     */
414
    public static void centreOnScreen(Component componentToMove) {
415
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
416
        componentToMove.setLocation((screenSize.width -
417
            componentToMove.getWidth()) / 2,
418
            (screenSize.height - componentToMove.getHeight()) / 2);
419
    }
420
421
    /**
422
     *  Centres the component on its window
423
     *
424
     *@param  componentToMove  Description of the Parameter
425
     */
426
    public static void centreOnWindow(Component componentToMove) {
427
        centre(componentToMove,
428
            SwingUtilities.windowForComponent(componentToMove));
429
    }
430
431
    /**
432
     *  Sets the column widths based on the first row.
433
     *
434
     *@param  table  Description of the Parameter
435
     */
436
    public static void chooseGoodColumnWidths(JTable table) {
437
        //Without padding, columns are slightly narrow, and we get "...". [Jon Aquino]
438
        final int PADDING = 5;
439
440
        if (table.getModel().getRowCount() == 0) {
441
            return;
442
        }
443
444
        for (int i = 0; i < table.getModel().getColumnCount(); i++) {
445
            TableColumn column = table.getColumnModel().getColumn(i);
446
            double headerWidth = table.getTableHeader().getDefaultRenderer()
447
                                      .getTableCellRendererComponent(table,
448
                    table.getModel().getColumnName(i), false, false, 0, i)
449
                                      .getPreferredSize().getWidth() + PADDING;
450
            double valueWidth = 10; // default in case of error
451
452
            try {
453
                valueWidth = table.getCellRenderer(0, i)
454
                                  .getTableCellRendererComponent(table,
455
                        table.getModel().getValueAt(0, i), false, false, 0, i)
456
                                  .getPreferredSize().getWidth() + PADDING;
457
            } catch (Exception ex) {
458
                // ignore the exception, since we can easily choose a default width
459
            }
460
461
            //Limit column width to 200 pixels.
462
            int width = Math.min(200,
463
                    Math.max((int) headerWidth, (int) valueWidth));
464
            column.setPreferredWidth(width);
465
466
            //Need to set the actual width too, otherwise actual width may end
467
            //up a bit less than the preferred width. [Jon Aquino]
468
            column.setWidth(width);
469
        }
470
    }
471
472
    public static JFileChooser createJFileChooserWithExistenceChecking() {
473
        return new JFileChooser() {
474
                public void approveSelection() {
475
                    File[] files = selectedFiles(this);
476
477
                    if (files.length == 0) {
478
                        return;
479
                    }
480
481
                    for (int i = 0; i < files.length; i++) {
482
                        if (!files[i].exists() && !files[i].isFile()) {
483
                            return;
484
                        }
485
                    }
486
487
                    super.approveSelection();
488
                }
489
            };
490
    }
491
492
    public static JFileChooser createJFileChooserWithOverwritePrompting() {
493
        return new JFileChooser() {
494
                public void approveSelection() {
495
                    if (selectedFiles(this).length != 1) {
496
                        return;
497
                    }
498
499
                    File selectedFile = selectedFiles(this)[0];
500
501
                    if (selectedFile.exists() && !selectedFile.isFile()) {
502
                        return;
503
                    }
504
505
                    if (selectedFile.exists()) {
506
                        int response = JOptionPane.showConfirmDialog(this,
507
                                "The file " + selectedFile.getName() +
508
                                " already exists. Do you " +
509
                                "want to replace the existing file?", "JUMP",
510
                                JOptionPane.YES_NO_OPTION);
511
512
                        if (response != JOptionPane.YES_OPTION) {
513
                            return;
514
                        }
515
                    }
516
517
                    super.approveSelection();
518
                }
519
            };
520
    }
521
522
    public static void doNotRoundDoubles(JTable table) {
523
        table.setDefaultRenderer(Double.class,
524
            new DefaultTableCellRenderer() {
525
                public void setValue(Object value) {
526
                    setText((value == null) ? "" : ("" + value));
527
                }
528
529
                {
530
                    setHorizontalAlignment(SwingConstants.RIGHT);
531
                }
532
            });
533
    }
534
535
    /**
536
     *  Workaround for Java Bug 4648654 "REGRESSION: Editable JComboBox focus
537
     *  misbehaves under Windows look and feel, proposed by Kleopatra
538
     *  (fastegal@addcom.de). Also see Java Bug 4673880 "REGRESSION: Modified
539
     *  editable JComboBox in Windows LAF does not release focus." This bug
540
     *  started occurring in Java 1.4.0.
541
     *
542
     *@param  cb  Description of the Parameter
543
     */
544
    public static void fixEditableComboBox(JComboBox cb) {
545
        Assert.isTrue(cb.isEditable());
546
547
        if (!UIManager.getLookAndFeel().getName().equals("Windows")) {
548
            return;
549
        }
550
551
        cb.setEditor(new BasicComboBoxEditor() {
552
                public void setItem(Object item) {
553
                    super.setItem(item);
554
                    editor.selectAll();
555
                }
556
            });
557
    }
558
559
    public static void handleThrowable(final Throwable t, final Component parent) {
560
        try {
561
            //<<TODO:UI>> A humane interface does not pop up an error dialog, as that interrupts
562
            //the user's work. Rather, error messages are displayed modelessly. See the book
563
            //"Humane Interfaces" (Raskin 2000) [Jon Aquino]
564
            SwingUtilities.invokeLater(new Runnable() {
565
                    public void run() {
566
                        t.printStackTrace(System.out);
567
                        JOptionPane.showMessageDialog(parent,
568
                            StringUtil.split(t.toString(), 80), "Exception",
569
                            JOptionPane.ERROR_MESSAGE);
570
                    }
571
                });
572
        } catch (Throwable t2) {
573
            t2.printStackTrace(System.out);
574
        }
575
    }
576
577
    /**
578
     * GUI operations should be performed only on the AWT event dispatching
579
     * thread. Blocks until the Runnable is finished.
580
     */
581
    public static void invokeOnEventThread(Runnable r)
582
        throws InterruptedException, InvocationTargetException {
583
        if (SwingUtilities.isEventDispatchThread()) {
584
            r.run();
585
        } else {
586
            SwingUtilities.invokeAndWait(r);
587
        }
588
    }
589
590
    public static String nameWithoutExtension(File file) {
591
        String name = file.getName();
592
        int dotPosition = name.indexOf('.');
593
594
        return (dotPosition < 0) ? name : name.substring(0, dotPosition);
595
    }
596
597
    public static void removeChoosableFileFilters(JFileChooser fc) {
598
        FileFilter[] filters = fc.getChoosableFileFilters();
599
600
        for (int i = 0; i < filters.length; i++) {
601
            fc.removeChoosableFileFilter(filters[i]);
602
        }
603
604
        return;
605
    }
606
607
    /**
608
     * @param extensions e.g. txt
609
     */
610
    public static FileFilter createFileFilter(final String description,
611
        final String[] extensions) {
612
        return new FileFilter() {
613
                public boolean accept(File f) {
614
                    if (f.isDirectory()) {
615
                        return true;
616
                    }
617
618
                    for (int i = 0; i < extensions.length; i++) {
619
                        if (GUIUtil.getExtension(f).equalsIgnoreCase(extensions[i])) {
620
                            return true;
621
                        }
622
                    }
623
624
                    return false;
625
                }
626
627
                public String getDescription() {
628
                    ArrayList extensionStrings = new ArrayList();
629
630
                    for (int i = 0; i < extensions.length; i++) {
631
                        extensionStrings.add("*." + extensions[i]);
632
                    }
633
634
                    return description + " (" +
635
                    StringUtil.replaceAll(StringUtil.toCommaDelimitedString(
636
                            extensionStrings), ",", ";") + ")";
637
                }
638
            };
639
    }
640
641
    /**
642
     *@param  color  a Color with possibly an alpha less than 255
643
     *@return        a Color with alpha equal to 255, but equivalent to the
644
     *      original translucent colour on a white background
645
     */
646
    public static Color toSimulatedTransparency(Color color) {
647
        //My guess, but it seems to work! [Jon Aquino]
648
        return new Color(color.getRed() +
649
            (int) (((255 - color.getRed()) * (255 - color.getAlpha())) / 255d),
650
            color.getGreen() +
651
            (int) (((255 - color.getGreen()) * (255 - color.getAlpha())) / 255d),
652
            color.getBlue() +
653
            (int) (((255 - color.getBlue()) * (255 - color.getAlpha())) / 255d));
654
    }
655
656
    public static String truncateString(String s, int maxLength) {
657
        if (s.length() < maxLength) {
658
            return s;
659
        }
660
661
        return s.substring(0, maxLength - 3) + "...";
662
    }
663
664
    public static Point2D subtract(Point2D a, Point2D b) {
665
        return new Point2D.Double(a.getX() - b.getX(), a.getY() - b.getY());
666
    }
667
668
    public static Point2D add(Point2D a, Point2D b) {
669
        return new Point2D.Double(a.getX() + b.getX(), a.getY() + b.getY());
670
    }
671
672
    public static Point2D multiply(Point2D v, double x) {
673
        return new Point2D.Double(v.getX() * x, v.getY() * x);
674
    }
675
676
    /**
677
     * The JVM's clipboard implementation is buggy (see bugs 4644554 and 4522198
678
     * in Sun's Java bug database). This method is a workaround that returns null
679
     * if an exception is thrown, as suggested in the bug reports.
680
     */
681
    public static Transferable getContents(Clipboard clipboard) {
682
        try {
683
            return clipboard.getContents(null);
684
        } catch (Throwable t) {
685
            return null;
686
        }
687
    }
688
689
    /**
690
     * Returns the distance from the baseline to the top of the text's bounding box.
691
     * Unlike the usual ascent, which is independent of the actual text.
692
     * Note that "True ascent" is not a standard term.
693
     */
694
    public static double trueAscent(TextLayout layout) {
695
        return -layout.getBounds().getY();
696
    }
697
698
    public static ImageIcon resize(ImageIcon icon, int extent) {
699
        return new ImageIcon(icon.getImage().getScaledInstance(extent, extent,
700
                Image.SCALE_SMOOTH));
701
    }
702
703
    /**
704
     * Resizes icon to 16 x 16.
705
     */
706
    public static ImageIcon toSmallIcon(ImageIcon icon) {
707
        return resize(icon, 16);
708
    }
709
710
    public static int swingThreadPriority() {
711
        final Int i = new Int();
712
713
        try {
714
            invokeOnEventThread(new Runnable() {
715
                    public void run() {
716
                        i.i = Thread.currentThread().getPriority();
717
                    }
718
                });
719
        } catch (InvocationTargetException e) {
720
            Assert.shouldNeverReachHere();
721
        } catch (InterruptedException e) {
722
            Assert.shouldNeverReachHere();
723
        }
724
725
        return i.i;
726
    }
727
728
    /**
729
     * Fix for Sun Java Bug 4398733: if you click in an inactive JInternalFrame,
730
     * the mousePressed and mouseReleased events will be fired, but not the
731
     * mouseClicked event.
732
     */
733
    public static void fixClicks(final Component c) {
734
        //This is a time bomb because when (if?) Sun fixes the bug, this method will
735
        //add an extra click. We should put an if statement here that immediately
736
        //returns if the Java version is greater than or equal to that in which the bug
737
        //is fixed. Problem is, we don't know what that version will be. [Jon Aquino]
738
        c.addMouseListener(new MouseListener() {
739
                public void mousePressed(MouseEvent e) {
740
                    add(e);
741
                }
742
743
                public void mouseExited(MouseEvent e) {
744
                    add(e);
745
                }
746
747
                public void mouseClicked(MouseEvent e) {
748
                    add(e);
749
                }
750
751
                public void mouseEntered(MouseEvent e) {
752
                    add(e);
753
                }
754
755
                private MouseEvent event(int i) {
756
                    return (MouseEvent) events.get(i);
757
                }
758
759
                public void mouseReleased(MouseEvent e) {
760
                    add(e);
761
762
                    if ((events.size() == 4) &&
763
                            (event(0).getID() == MouseEvent.MOUSE_PRESSED) &&
764
                            (event(1).getID() == MouseEvent.MOUSE_EXITED) &&
765
                            (event(2).getID() == MouseEvent.MOUSE_ENTERED)) {
766
                        c.dispatchEvent(new MouseEvent(c,
767
                                MouseEvent.MOUSE_CLICKED,
768
                                System.currentTimeMillis(), e.getModifiers(),
769
                                e.getX(), e.getY(), e.getClickCount(),
770
                                e.isPopupTrigger()));
771
                    }
772
                }
773
774
                private void add(MouseEvent e) {
775
                    if (events.size() == 4) {
776
                        events.remove(0);
777
                    }
778
779
                    events.add(e);
780
                }
781
782
                private ArrayList events = new ArrayList();
783
            });
784
    }
785
786
    /**
787
     * Listens to all internal frames (current and future) in a JDesktopPane.
788
     */
789
    public static void addInternalFrameListener(JDesktopPane pane,
790
        final InternalFrameListener listener) {
791
        JInternalFrame[] frames = pane.getAllFrames();
792
793
        for (int i = 0; i < frames.length; i++) {
794
            frames[i].addInternalFrameListener(listener);
795
        }
796
797
        pane.addContainerListener(new ContainerAdapter() {
798
                public void componentAdded(ContainerEvent e) {
799
                    if (e.getChild() instanceof JInternalFrame) {
800
                        ((JInternalFrame) e.getChild()).removeInternalFrameListener(listener);
801
                        ((JInternalFrame) e.getChild()).addInternalFrameListener(listener);
802
                    }
803
                }
804
            });
805
    }
806
807
    public static DocumentListener toDocumentListener(
808
        final ActionListener listener) {
809
        return new DocumentListener() {
810
                public void insertUpdate(DocumentEvent e) {
811
                    listener.actionPerformed(new ActionEvent(e, 0, e.toString()));
812
                }
813
814
                public void removeUpdate(DocumentEvent e) {
815
                    listener.actionPerformed(new ActionEvent(e, 0, e.toString()));
816
                }
817
818
                public void changedUpdate(DocumentEvent e) {
819
                    listener.actionPerformed(new ActionEvent(e, 0, e.toString()));
820
                }
821
            };
822
    }
823
824
    public static ListDataListener toListDataListener(
825
        final ActionListener listener) {
826
        return new ListDataListener() {
827
                public void intervalAdded(ListDataEvent e) {
828
                    listener.actionPerformed(new ActionEvent(e.getSource(), 0,
829
                            e.toString()));
830
                }
831
832
                public void intervalRemoved(ListDataEvent e) {
833
                    listener.actionPerformed(new ActionEvent(e.getSource(), 0,
834
                            e.toString()));
835
                }
836
837
                public void contentsChanged(ListDataEvent e) {
838
                    listener.actionPerformed(null);
839
                }
840
            };
841
    }
842
843
    public static InternalFrameListener toInternalFrameListener(
844
        final ActionListener listener) {
845
        return new InternalFrameListener() {
846
                private void fireActionPerformed(InternalFrameEvent e) {
847
                    listener.actionPerformed(new ActionEvent(e.getSource(),
848
                            e.getID(), e.toString()));
849
                }
850
851
                public void internalFrameActivated(InternalFrameEvent e) {
852
                    fireActionPerformed(e);
853
                }
854
855
                public void internalFrameClosed(InternalFrameEvent e) {
856
                    fireActionPerformed(e);
857
                }
858
859
                public void internalFrameClosing(InternalFrameEvent e) {
860
                    fireActionPerformed(e);
861
                }
862
863
                public void internalFrameDeactivated(InternalFrameEvent e) {
864
                    fireActionPerformed(e);
865
                }
866
867
                public void internalFrameDeiconified(InternalFrameEvent e) {
868
                    fireActionPerformed(e);
869
                }
870
871
                public void internalFrameIconified(InternalFrameEvent e) {
872
                    fireActionPerformed(e);
873
                }
874
875
                public void internalFrameOpened(InternalFrameEvent e) {
876
                    fireActionPerformed(e);
877
                }
878
            };
879
    }
880
881
    /**
882
     * Returns a Timer that fires once, after the delay. The delay can be restarted
883
     * by restarting the Timer.
884
     */
885
    public static Timer createRestartableSingleEventTimer(int delay,
886
        ActionListener listener) {
887
        Timer timer = new Timer(delay, listener);
888
        timer.setCoalesce(true);
889
        timer.setInitialDelay(delay);
890
        timer.setRepeats(false);
891
892
        return timer;
893
    }
894
895
    public static ValidatingTextField createSyncdTextField(JSlider s) {
896
        int columns = (int) Math.ceil(Math.log(s.getMaximum()) / Math.log(10));
897
898
        return createSyncdTextField(s, columns);
899
    }
900
901
    public static ValidatingTextField createSyncdTextField(JSlider s,
902
        int columns) {
903
        ValidatingTextField t = new ValidatingTextField(s.getValue() + "",
904
                columns, SwingConstants.RIGHT,
905
                ValidatingTextField.INTEGER_VALIDATOR,
906
                new ValidatingTextField.CompositeCleaner(new ValidatingTextField.Cleaner[] {
907
                        new ValidatingTextField.BlankCleaner("" +
908
                            s.getMinimum()),
909
                        new ValidatingTextField.MinIntCleaner(s.getMinimum()),
910
                        new ValidatingTextField.MaxIntCleaner(s.getMaximum())
911
                    }));
912
        sync(s, t);
913
        syncEnabledStates(s, t);
914
915
        return t;
916
    }
917
918
    /**
919
     * @see #createSyncdTextField(JSlider s, int columns)
920
     */
921
     public static void sync(final JSlider s, final ValidatingTextField t) {
922
        t.setText("" + s.getValue());
923
924
        final Boolean[] changing = new Boolean[] { Boolean.FALSE };
925
        s.addChangeListener(new ChangeListener() {
926
                public void stateChanged(ChangeEvent e) {
927
                    if (changing[0] == Boolean.TRUE) {
928
                        return;
929
                    }
930
931
                    changing[0] = Boolean.TRUE;
932
933
                    try {
934
                        t.setText("" + s.getValue());
935
                    } finally {
936
                        changing[0] = Boolean.FALSE;
937
                    }
938
                }
939
            });
940
        t.getDocument().addDocumentListener(new DocumentListener() {
941
                private void changed() {
942
                    if (changing[0] == Boolean.TRUE) {
943
                        return;
944
                    }
945
946
                    changing[0] = Boolean.TRUE;
947
948
                    try {
949
                        s.setValue(t.getInteger());
950
                    } finally {
951
                        changing[0] = Boolean.FALSE;
952
                    }
953
                }
954
955
                public void changedUpdate(DocumentEvent e) {
956
                    changed();
957
                }
958
959
                public void insertUpdate(DocumentEvent e) {
960
                    changed();
961
                }
962
963
                public void removeUpdate(DocumentEvent e) {
964
                    changed();
965
                }
966
            });
967
    }
968
969
    public static void syncEnabledStates(final JComponent c1,
970
        final JComponent c2) {
971
        c2.setEnabled(c1.isEnabled());
972
        c1.addPropertyChangeListener("enabled",
973
            new PropertyChangeListener() {
974
                public void propertyChange(PropertyChangeEvent evt) {
975
                    if (c1.isEnabled() == c2.isEnabled()) {
976
                        return;
977
                    }
978
979
                    c2.setEnabled(c1.isEnabled());
980
                }
981
            });
982
        c2.addPropertyChangeListener("enabled",
983
            new PropertyChangeListener() {
984
                public void propertyChange(PropertyChangeEvent evt) {
985
                    if (c1.isEnabled() == c2.isEnabled()) {
986
                        return;
987
                    }
988
989
                    c1.setEnabled(c2.isEnabled());
990
                }
991
            });
992
    }
993
994
    public static void sync(final JSlider s1, final JSlider s2) {
995
        s2.setValue(s1.getValue());
996
        Assert.isTrue(s1.getMinimum() == s2.getMinimum());
997
        Assert.isTrue(s1.getMaximum() == s2.getMaximum());
998
999
        final Boolean[] changing = new Boolean[] { Boolean.FALSE };
1000
        s1.addChangeListener(new ChangeListener() {
1001
                public void stateChanged(ChangeEvent e) {
1002
                    if (changing[0] == Boolean.TRUE) {
1003
                        return;
1004
                    }
1005
1006
                    changing[0] = Boolean.TRUE;
1007
1008
                    try {
1009
                        s2.setValue(s1.getValue());
1010
                    } finally {
1011
                        changing[0] = Boolean.FALSE;
1012
                    }
1013
                }
1014
            });
1015
        s2.addChangeListener(new ChangeListener() {
1016
                public void stateChanged(ChangeEvent e) {
1017
                    if (changing[0] == Boolean.TRUE) {
1018
                        return;
1019
                    }
1020
1021
                    changing[0] = Boolean.TRUE;
1022
1023
                    try {
1024
                        s1.setValue(s2.getValue());
1025
                    } finally {
1026
                        changing[0] = Boolean.FALSE;
1027
                    }
1028
                }
1029
            });
1030
    }
1031
1032
    public static void sync(final JCheckBox c1, final JCheckBox c2) {
1033
        c2.setSelected(c1.isSelected());
1034
1035
        final Boolean[] changing = new Boolean[] { Boolean.FALSE };
1036
        c1.addActionListener(new ActionListener() {
1037
                public void actionPerformed(ActionEvent e) {
1038
                    if (changing[0] == Boolean.TRUE) {
1039
                        return;
1040
                    }
1041
1042
                    changing[0] = Boolean.TRUE;
1043
1044
                    try {
1045
                        c2.setSelected(c1.isSelected());
1046
                    } finally {
1047
                        changing[0] = Boolean.FALSE;
1048
                    }
1049
                }
1050
            });
1051
        c2.addActionListener(new ActionListener() {
1052
                public void actionPerformed(ActionEvent e) {
1053
                    if (changing[0] == Boolean.TRUE) {
1054
                        return;
1055
                    }
1056
1057
                    changing[0] = Boolean.TRUE;
1058
1059
                    try {
1060
                        c1.setSelected(c2.isSelected());
1061
                    } finally {
1062
                        changing[0] = Boolean.FALSE;
1063
                    }
1064
                }
1065
            });
1066
    }
1067
1068
    public static List items(JComboBox comboBox) {
1069
        ArrayList items = new ArrayList();
1070
1071
        for (int i = 0; i < comboBox.getItemCount(); i++) {
1072
            items.add(comboBox.getItemAt(i));
1073
        }
1074
1075
        return items;
1076
    }
1077
1078
    /**
1079
     * Calls #doClick so that events are fired.
1080
     */
1081
    public static void setSelectedWithClick(JCheckBox checkBox, boolean selected) {
1082
        checkBox.setSelected(!selected);
1083
        checkBox.doClick();
1084
    }
1085
1086
    public static void setLocation(Component componentToMove,
1087
        Location location, Component other) {
1088
        Point p = new Point((int) other.getLocationOnScreen().getX() +
1089
                (location.fromRight
1090
                ? (other.getWidth() - componentToMove.getWidth() - location.x)
1091
                : location.x),
1092
                (int) other.getLocationOnScreen().getY() +
1093
                (location.fromBottom
1094
                ? (other.getHeight() - componentToMove.getHeight() -
1095
                location.y) : location.y));
1096
        SwingUtilities.convertPointFromScreen(p, componentToMove.getParent());
1097
        componentToMove.setLocation(p);
1098
    }
1099
1100
1101
    /** Highlights a given component with a given color.
1102
     * Great for GridBagLayout debugging.
1103
     *
1104
     * @author Jon Aquino
1105
     */
1106
    public static void highlightForDebugging(JComponent component, Color color) {
1107
        component.setBackground(color);
1108
        component.setBorder(BorderFactory.createMatteBorder(10, 10, 10, 10,
1109
                color));
1110
    }
1111
1112
    public static Component topCard(Container c) {
1113
        Assert.isTrue(c.getLayout() instanceof CardLayout);
1114
1115
        Component[] components = c.getComponents();
1116
1117
        for (int i = 0; i < components.length; i++) {
1118
            if (components[i].isVisible()) {
1119
                return components[i];
1120
            }
1121
        }
1122
1123
        Assert.shouldNeverReachHere();
1124
1125
        return null;
1126
    }
1127
1128
    /**
1129
     * Work around Java Bug 4437688 "JFileChooser.getSelectedFile() returns
1130
     * nothing when a file is selected" [Jon Aquino]
1131
     */
1132
    public static File[] selectedFiles(JFileChooser chooser) {
1133
        return ((chooser.getSelectedFiles().length == 0) &&
1134
        (chooser.getSelectedFile() != null))
1135
        ? new File[] { chooser.getSelectedFile() } : chooser.getSelectedFiles();
1136
    }
1137
1138
    public static ImageIcon toDisabledIcon(ImageIcon icon) {
1139
        return new ImageIcon(GrayFilter.createDisabledImage((icon).getImage()));
1140
    }
1141
1142
    public static Component getDescendantOfClass(Class c, Container container) {
1143
        for (int i = 0; i < container.getComponentCount(); i++) {
1144
            if (c.isInstance(container.getComponent(i))) {
1145
                return container.getComponent(i);
1146
            }
1147
1148
            if (container.getComponent(i) instanceof Container) {
1149
                Component descendant = getDescendantOfClass(c,
1150
                        (Container) container.getComponent(i));
1151
1152
                if (descendant != null) {
1153
                    return descendant;
1154
                }
1155
            }
1156
        }
1157
1158
        return null;
1159
    }
1160
1161
    /**
1162
     * Ensures that the next frame is activated when #dispose is called
1163
     * explicitly, in JDK 1.4. JDK 1.3 didn't have this problem.
1164
     */
1165
    public static void dispose(final JInternalFrame internalFrame,
1166
        JDesktopPane desktopPane) {
1167
        desktopPane.getDesktopManager().closeFrame(internalFrame);
1168
        internalFrame.dispose();
1169
    }
1170
1171
    private static class Int {
1172
        public volatile int i;
1173
    }
1174
1175
    public static class Location {
1176
        private int x;
1177
        private int y;
1178
        private boolean fromRight;
1179
        private boolean fromBottom;
1180
1181
        /**
1182
         * Constructor taking an initial location, offset hint.
1183
         *
1184
         * @param fromBottom whether y is the number of pixels between the bottom
1185
         * edges of the toolbox and desktop pane, or between the top edges.
1186
         */
1187
        public Location(int x, boolean fromRight, int y, boolean fromBottom) {
1188
            this.x = x;
1189
            this.y = y;
1190
            this.fromRight = fromRight;
1191
            this.fromBottom = fromBottom;
1192
        }
1193
    }
1194
}