Statistics
| Revision:

svn-gvsig-desktop / tags / v1_0_2_Build_910 / applications / appgvSIG / src / com / iver / cit / gvsig / gui / GUIUtil.java @ 11275

History | View | Annotate | Download (37.4 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
/* 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
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
import com.vividsolutions.jts.util.Assert;
129
import com.vividsolutions.jump.util.StringUtil;
130

    
131

    
132
//<<TODO:NAMING>> Perhaps rename to WorkbenchUtilities and move to workbench package? [Jon Aquino]
133
public class GUIUtil {
134
    public final static String dbf = "dbf";
135
    public final static String dbfDesc = "DBF";
136
    public final static String fme = "fme";
137
    public final static String fmeDesc = "FME GML";
138
    public final static String gml = "gml";
139
    public final static String gmlDesc = "GML";
140

    
141
    //<<TODO:REFACTORING>> If these constants are only used by descendants of
142
    //AbstractDriver, they should be moved to AbstractDriver. GUIUtilities is
143
    //supposed to be very generic. [Jon Aquino]
144
    public final static String jml = "jml";
145
    public final static String jmlDesc = "JCS GML";
146
    public final static String shp = "shp";
147

    
148
    //<<TODO:NAMING>> "ESRI Shapefile" would be more precise. Is this what they
149
    //are? [Jon Aquino]
150
    public final static String shpDesc = "ESRI Shapefile";
151
    public final static String shx = "shx";
152
    public final static String shxDesc = "SHX";
153
    public final static String wkt = "wkt";
154
    public final static String wktDesc = "Well Known Text";
155
    public final static String wktaDesc = "Well Known Text (Show Attribute)";
156
    public final static String xml = "xml";
157
    public final static String xmlDesc = "XML";
158
    public static final FileFilter ALL_FILES_FILTER = new FileFilter() {
159
            public boolean accept(File f) {
160
                return true;
161
            }
162

    
163
            public String getDescription() {
164
                return "All Files";
165
            }
166
        };
167

    
168
    public GUIUtil() {
169
    }
170

    
171
    /**
172
     * Returns a string suitable for embeddind as HTML.  That is, all 
173
     * characters which have a special meaning in HTML are escaped
174
     * as character codes.
175
     * 
176
     * <p>
177
     * Based on code from Jason Sherman. See http://www.w3schools.com/html/html_asciiref.asp
178
     * </p>
179
     */
180
    public final static String escapeHTML(String value, boolean escapeSpaces,
181
        boolean escapeNewlines) {
182
        if (value == null) {
183
            return (null);
184
        }
185

    
186
        char[] content = new char[value.length()];
187
        value.getChars(0, value.length(), content, 0);
188

    
189
        StringBuffer result = new StringBuffer();
190

    
191
        for (int i = 0; i < content.length; i++) {
192
            switch (content[i]) {
193
            case ' ':
194
                result.append(escapeSpaces ? "&#32;" : " ");
195

    
196
                break;
197

    
198
            //Added \n [Jon Aquino]
199
            case '\n':
200
                result.append(escapeNewlines ? "<BR>" : "\n");
201

    
202
                break;
203

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

    
207
                break;
208

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

    
212
                break;
213

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

    
217
                break;
218

    
219
            case '$':
220
                result.append("&#36;");
221

    
222
                break;
223

    
224
            case '%':
225
                result.append("&#37;");
226

    
227
                break;
228

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

    
232
                break;
233

    
234
            case '\'':
235
                result.append("&#39;");
236

    
237
                break;
238

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

    
242
                break;
243

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

    
247
                break;
248

    
249
            case '*':
250
                result.append("&#42;");
251

    
252
                break;
253

    
254
            case '+':
255
                result.append("&#43;");
256

    
257
                break;
258

    
259
            case ',':
260
                result.append("&#44;");
261

    
262
                break;
263

    
264
            case '-':
265
                result.append("&#45;");
266

    
267
                break;
268

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

    
272
                break;
273

    
274
            case '/':
275
                result.append("&#47;");
276

    
277
                break;
278

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

    
282
                break;
283

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

    
287
                break;
288

    
289
            case '<':
290
                result.append("&#60;");
291

    
292
                break;
293

    
294
            case '=':
295
                result.append("&#61;");
296

    
297
                break;
298

    
299
            case '>':
300
                result.append("&#62;");
301

    
302
                break;
303

    
304
            case '?':
305
                result.append("&#63;");
306

    
307
                break;
308

    
309
            case '@':
310
                result.append("&#64;");
311

    
312
                break;
313

    
314
            case '[':
315
                result.append("&#91;");
316

    
317
                break;
318

    
319
            case '\\':
320
                result.append("&#92;");
321

    
322
                break;
323

    
324
            case ']':
325
                result.append("&#93;");
326

    
327
                break;
328

    
329
            case '^':
330
                result.append("&#94;");
331

    
332
                break;
333

    
334
            case '_':
335
                result.append("&#95;");
336

    
337
                break;
338

    
339
            case '`':
340
                result.append("&#96;");
341

    
342
                break;
343

    
344
            case '{':
345
                result.append("&#123;");
346

    
347
                break;
348

    
349
            case '|':
350
                result.append("&#124;");
351

    
352
                break;
353

    
354
            case '}':
355
                result.append("&#125;");
356

    
357
                break;
358

    
359
            case '~':
360
                result.append("&#126;");
361

    
362
                break;
363

    
364
            default:
365
                result.append(content[i]);
366
            }
367
        }
368

    
369
        return (result.toString());
370
    }
371

    
372
    /*
373
     *  Get the extension of a file e.g. txt
374
     */
375
    public static String getExtension(File f) {
376
        String ext = "";
377
        String s = f.getName();
378
        int i = s.lastIndexOf('.');
379

    
380
        if ((i > 0) && (i < (s.length() - 1))) {
381
            ext = s.substring(i + 1).toLowerCase();
382
        }
383

    
384
        return ext;
385
    }
386

    
387
    public static Color alphaColor(Color color, int alpha) {
388
        return new Color(color.getRed(), color.getGreen(), color.getBlue(),
389
            alpha);
390
    }
391

    
392
    /**
393
     *  Centres the first component on the second
394
     *
395
     *@param  componentToMove      Description of the Parameter
396
     *@param  componentToCentreOn  Description of the Parameter
397
     */
398
    public static void centre(Component componentToMove,
399
        Component componentToCentreOn) {
400
        Dimension componentToCentreOnSize = componentToCentreOn.getSize();
401
        componentToMove.setLocation(componentToCentreOn.getX() +
402
            ((componentToCentreOnSize.width - componentToMove.getWidth()) / 2),
403
            componentToCentreOn.getY() +
404
            ((componentToCentreOnSize.height - componentToMove.getHeight()) / 2));
405
    }
406

    
407
    /**
408
     *  Centres the component on the screen
409
     *
410
     *@param  componentToMove  Description of the Parameter
411
     */
412
    public static void centreOnScreen(Component componentToMove) {
413
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
414
        componentToMove.setLocation((screenSize.width -
415
            componentToMove.getWidth()) / 2,
416
            (screenSize.height - componentToMove.getHeight()) / 2);
417
    }
418

    
419
    /**
420
     *  Centres the component on its window
421
     *
422
     *@param  componentToMove  Description of the Parameter
423
     */
424
    public static void centreOnWindow(Component componentToMove) {
425
        centre(componentToMove,
426
            SwingUtilities.windowForComponent(componentToMove));
427
    }
428

    
429
    /**
430
     *  Sets the column widths based on the first row.
431
     *
432
     *@param  table  Description of the Parameter
433
     */
434
    public static void chooseGoodColumnWidths(JTable table) {
435
        //Without padding, columns are slightly narrow, and we get "...". [Jon Aquino]
436
        final int PADDING = 5;
437

    
438
        if (table.getModel().getRowCount() == 0) {
439
            return;
440
        }
441

    
442
        for (int i = 0; i < table.getModel().getColumnCount(); i++) {
443
            TableColumn column = table.getColumnModel().getColumn(i);
444
            double headerWidth = table.getTableHeader().getDefaultRenderer()
445
                                      .getTableCellRendererComponent(table,
446
                    table.getModel().getColumnName(i), false, false, 0, i)
447
                                      .getPreferredSize().getWidth() + PADDING;
448
            double valueWidth = 10; // default in case of error
449

    
450
            try {
451
                valueWidth = table.getCellRenderer(0, i)
452
                                  .getTableCellRendererComponent(table,
453
                        table.getModel().getValueAt(0, i), false, false, 0, i)
454
                                  .getPreferredSize().getWidth() + PADDING;
455
            } catch (Exception ex) {
456
                // ignore the exception, since we can easily choose a default width
457
            }
458

    
459
            //Limit column width to 200 pixels.
460
            int width = Math.min(200,
461
                    Math.max((int) headerWidth, (int) valueWidth));
462
            column.setPreferredWidth(width);
463

    
464
            //Need to set the actual width too, otherwise actual width may end
465
            //up a bit less than the preferred width. [Jon Aquino]
466
            column.setWidth(width);
467
        }
468
    }
469

    
470
    public static JFileChooser createJFileChooserWithExistenceChecking() {
471
        return new JFileChooser() {
472
                public void approveSelection() {
473
                    File[] files = selectedFiles(this);
474

    
475
                    if (files.length == 0) {
476
                        return;
477
                    }
478

    
479
                    for (int i = 0; i < files.length; i++) {
480
                        if (!files[i].exists() && !files[i].isFile()) {
481
                            return;
482
                        }
483
                    }
484

    
485
                    super.approveSelection();
486
                }
487
            };
488
    }
489

    
490
    public static JFileChooser createJFileChooserWithOverwritePrompting() {
491
        return new JFileChooser() {
492
                public void approveSelection() {
493
                    if (selectedFiles(this).length != 1) {
494
                        return;
495
                    }
496

    
497
                    File selectedFile = selectedFiles(this)[0];
498

    
499
                    if (selectedFile.exists() && !selectedFile.isFile()) {
500
                        return;
501
                    }
502

    
503
                    if (selectedFile.exists()) {
504
                        int response = JOptionPane.showConfirmDialog(this,
505
                                "The file " + selectedFile.getName() +
506
                                " already exists. Do you " +
507
                                "want to replace the existing file?", "JUMP",
508
                                JOptionPane.YES_NO_OPTION);
509

    
510
                        if (response != JOptionPane.YES_OPTION) {
511
                            return;
512
                        }
513
                    }
514

    
515
                    super.approveSelection();
516
                }
517
            };
518
    }
519

    
520
    public static void doNotRoundDoubles(JTable table) {
521
        table.setDefaultRenderer(Double.class,
522
            new DefaultTableCellRenderer() {
523
                public void setValue(Object value) {
524
                    setText((value == null) ? "" : ("" + value));
525
                }
526

    
527
                {
528
                    setHorizontalAlignment(SwingConstants.RIGHT);
529
                }
530
            });
531
    }
532

    
533
    /**
534
     *  Workaround for Java Bug 4648654 "REGRESSION: Editable JComboBox focus
535
     *  misbehaves under Windows look and feel, proposed by Kleopatra
536
     *  (fastegal@addcom.de). Also see Java Bug 4673880 "REGRESSION: Modified
537
     *  editable JComboBox in Windows LAF does not release focus." This bug
538
     *  started occurring in Java 1.4.0.
539
     *
540
     *@param  cb  Description of the Parameter
541
     */
542
    public static void fixEditableComboBox(JComboBox cb) {
543
        Assert.isTrue(cb.isEditable());
544

    
545
        if (!UIManager.getLookAndFeel().getName().equals("Windows")) {
546
            return;
547
        }
548

    
549
        cb.setEditor(new BasicComboBoxEditor() {
550
                public void setItem(Object item) {
551
                    super.setItem(item);
552
                    editor.selectAll();
553
                }
554
            });
555
    }
556

    
557
    public static void handleThrowable(final Throwable t, final Component parent) {
558
        try {
559
            //<<TODO:UI>> A humane interface does not pop up an error dialog, as that interrupts
560
            //the user's work. Rather, error messages are displayed modelessly. See the book
561
            //"Humane Interfaces" (Raskin 2000) [Jon Aquino]
562
            SwingUtilities.invokeLater(new Runnable() {
563
                    public void run() {
564
                        t.printStackTrace(System.out);
565
                        JOptionPane.showMessageDialog(parent,
566
                            StringUtil.split(t.toString(), 80), "Exception",
567
                            JOptionPane.ERROR_MESSAGE);
568
                    }
569
                });
570
        } catch (Throwable t2) {
571
            t2.printStackTrace(System.out);
572
        }
573
    }
574

    
575
    /**
576
     * GUI operations should be performed only on the AWT event dispatching
577
     * thread. Blocks until the Runnable is finished.
578
     */
579
    public static void invokeOnEventThread(Runnable r)
580
        throws InterruptedException, InvocationTargetException {
581
        if (SwingUtilities.isEventDispatchThread()) {
582
            r.run();
583
        } else {
584
            SwingUtilities.invokeAndWait(r);
585
        }
586
    }
587

    
588
    public static String nameWithoutExtension(File file) {
589
        String name = file.getName();
590
        int dotPosition = name.indexOf('.');
591

    
592
        return (dotPosition < 0) ? name : name.substring(0, dotPosition);
593
    }
594

    
595
    public static void removeChoosableFileFilters(JFileChooser fc) {
596
        FileFilter[] filters = fc.getChoosableFileFilters();
597

    
598
        for (int i = 0; i < filters.length; i++) {
599
            fc.removeChoosableFileFilter(filters[i]);
600
        }
601

    
602
        return;
603
    }
604

    
605
    /**
606
     * @param extensions e.g. txt
607
     */
608
    public static FileFilter createFileFilter(final String description,
609
        final String[] extensions) {
610
        return new FileFilter() {
611
                public boolean accept(File f) {
612
                    if (f.isDirectory()) {
613
                        return true;
614
                    }
615

    
616
                    for (int i = 0; i < extensions.length; i++) {
617
                        if (GUIUtil.getExtension(f).equalsIgnoreCase(extensions[i])) {
618
                            return true;
619
                        }
620
                    }
621

    
622
                    return false;
623
                }
624

    
625
                public String getDescription() {
626
                    ArrayList extensionStrings = new ArrayList();
627

    
628
                    for (int i = 0; i < extensions.length; i++) {
629
                        extensionStrings.add("*." + extensions[i]);
630
                    }
631

    
632
                    return description + " (" +
633
                    StringUtil.replaceAll(StringUtil.toCommaDelimitedString(
634
                            extensionStrings), ",", ";") + ")";
635
                }
636
            };
637
    }
638

    
639
    /**
640
     *@param  color  a Color with possibly an alpha less than 255
641
     *@return        a Color with alpha equal to 255, but equivalent to the
642
     *      original translucent colour on a white background
643
     */
644
    public static Color toSimulatedTransparency(Color color) {
645
        //My guess, but it seems to work! [Jon Aquino]
646
        return new Color(color.getRed() +
647
            (int) (((255 - color.getRed()) * (255 - color.getAlpha())) / 255d),
648
            color.getGreen() +
649
            (int) (((255 - color.getGreen()) * (255 - color.getAlpha())) / 255d),
650
            color.getBlue() +
651
            (int) (((255 - color.getBlue()) * (255 - color.getAlpha())) / 255d));
652
    }
653

    
654
    public static String truncateString(String s, int maxLength) {
655
        if (s.length() < maxLength) {
656
            return s;
657
        }
658

    
659
        return s.substring(0, maxLength - 3) + "...";
660
    }
661

    
662
    public static Point2D subtract(Point2D a, Point2D b) {
663
        return new Point2D.Double(a.getX() - b.getX(), a.getY() - b.getY());
664
    }
665

    
666
    public static Point2D add(Point2D a, Point2D b) {
667
        return new Point2D.Double(a.getX() + b.getX(), a.getY() + b.getY());
668
    }
669

    
670
    public static Point2D multiply(Point2D v, double x) {
671
        return new Point2D.Double(v.getX() * x, v.getY() * x);
672
    }
673

    
674
    /**
675
     * The JVM's clipboard implementation is buggy (see bugs 4644554 and 4522198
676
     * in Sun's Java bug database). This method is a workaround that returns null
677
     * if an exception is thrown, as suggested in the bug reports.
678
     */
679
    public static Transferable getContents(Clipboard clipboard) {
680
        try {
681
            return clipboard.getContents(null);
682
        } catch (Throwable t) {
683
            return null;
684
        }
685
    }
686

    
687
    /**
688
     * Returns the distance from the baseline to the top of the text's bounding box.
689
     * Unlike the usual ascent, which is independent of the actual text.
690
     * Note that "True ascent" is not a standard term.
691
     */
692
    public static double trueAscent(TextLayout layout) {
693
        return -layout.getBounds().getY();
694
    }
695

    
696
    public static ImageIcon resize(ImageIcon icon, int extent) {
697
        return new ImageIcon(icon.getImage().getScaledInstance(extent, extent,
698
                Image.SCALE_SMOOTH));
699
    }
700

    
701
    /**
702
     * Resizes icon to 16 x 16.
703
     */
704
    public static ImageIcon toSmallIcon(ImageIcon icon) {
705
        return resize(icon, 16);
706
    }
707

    
708
    public static int swingThreadPriority() {
709
        final Int i = new Int();
710

    
711
        try {
712
            invokeOnEventThread(new Runnable() {
713
                    public void run() {
714
                        i.i = Thread.currentThread().getPriority();
715
                    }
716
                });
717
        } catch (InvocationTargetException e) {
718
            Assert.shouldNeverReachHere();
719
        } catch (InterruptedException e) {
720
            Assert.shouldNeverReachHere();
721
        }
722

    
723
        return i.i;
724
    }
725

    
726
    /**
727
     * Fix for Sun Java Bug 4398733: if you click in an inactive JInternalFrame,
728
     * the mousePressed and mouseReleased events will be fired, but not the
729
     * mouseClicked event.
730
     */
731
    public static void fixClicks(final Component c) {
732
        //This is a time bomb because when (if?) Sun fixes the bug, this method will
733
        //add an extra click. We should put an if statement here that immediately
734
        //returns if the Java version is greater than or equal to that in which the bug
735
        //is fixed. Problem is, we don't know what that version will be. [Jon Aquino]
736
        c.addMouseListener(new MouseListener() {
737
                public void mousePressed(MouseEvent e) {
738
                    add(e);
739
                }
740

    
741
                public void mouseExited(MouseEvent e) {
742
                    add(e);
743
                }
744

    
745
                public void mouseClicked(MouseEvent e) {
746
                    add(e);
747
                }
748

    
749
                public void mouseEntered(MouseEvent e) {
750
                    add(e);
751
                }
752

    
753
                private MouseEvent event(int i) {
754
                    return (MouseEvent) events.get(i);
755
                }
756

    
757
                public void mouseReleased(MouseEvent e) {
758
                    add(e);
759

    
760
                    if ((events.size() == 4) &&
761
                            (event(0).getID() == MouseEvent.MOUSE_PRESSED) &&
762
                            (event(1).getID() == MouseEvent.MOUSE_EXITED) &&
763
                            (event(2).getID() == MouseEvent.MOUSE_ENTERED)) {
764
                        c.dispatchEvent(new MouseEvent(c,
765
                                MouseEvent.MOUSE_CLICKED,
766
                                System.currentTimeMillis(), e.getModifiers(),
767
                                e.getX(), e.getY(), e.getClickCount(),
768
                                e.isPopupTrigger()));
769
                    }
770
                }
771

    
772
                private void add(MouseEvent e) {
773
                    if (events.size() == 4) {
774
                        events.remove(0);
775
                    }
776

    
777
                    events.add(e);
778
                }
779

    
780
                private ArrayList events = new ArrayList();
781
            });
782
    }
783

    
784
    /**
785
     * Listens to all internal frames (current and future) in a JDesktopPane.
786
     */
787
    public static void addInternalFrameListener(JDesktopPane pane,
788
        final InternalFrameListener listener) {
789
        JInternalFrame[] frames = pane.getAllFrames();
790

    
791
        for (int i = 0; i < frames.length; i++) {
792
            frames[i].addInternalFrameListener(listener);
793
        }
794

    
795
        pane.addContainerListener(new ContainerAdapter() {
796
                public void componentAdded(ContainerEvent e) {
797
                    if (e.getChild() instanceof JInternalFrame) {
798
                        ((JInternalFrame) e.getChild()).removeInternalFrameListener(listener);
799
                        ((JInternalFrame) e.getChild()).addInternalFrameListener(listener);
800
                    }
801
                }
802
            });
803
    }
804

    
805
    public static DocumentListener toDocumentListener(
806
        final ActionListener listener) {
807
        return new DocumentListener() {
808
                public void insertUpdate(DocumentEvent e) {
809
                    listener.actionPerformed(new ActionEvent(e, 0, e.toString()));
810
                }
811

    
812
                public void removeUpdate(DocumentEvent e) {
813
                    listener.actionPerformed(new ActionEvent(e, 0, e.toString()));
814
                }
815

    
816
                public void changedUpdate(DocumentEvent e) {
817
                    listener.actionPerformed(new ActionEvent(e, 0, e.toString()));
818
                }
819
            };
820
    }
821

    
822
    public static ListDataListener toListDataListener(
823
        final ActionListener listener) {
824
        return new ListDataListener() {
825
                public void intervalAdded(ListDataEvent e) {
826
                    listener.actionPerformed(new ActionEvent(e.getSource(), 0,
827
                            e.toString()));
828
                }
829

    
830
                public void intervalRemoved(ListDataEvent e) {
831
                    listener.actionPerformed(new ActionEvent(e.getSource(), 0,
832
                            e.toString()));
833
                }
834

    
835
                public void contentsChanged(ListDataEvent e) {
836
                    listener.actionPerformed(null);
837
                }
838
            };
839
    }
840

    
841
    public static InternalFrameListener toInternalFrameListener(
842
        final ActionListener listener) {
843
        return new InternalFrameListener() {
844
                private void fireActionPerformed(InternalFrameEvent e) {
845
                    listener.actionPerformed(new ActionEvent(e.getSource(),
846
                            e.getID(), e.toString()));
847
                }
848

    
849
                public void internalFrameActivated(InternalFrameEvent e) {
850
                    fireActionPerformed(e);
851
                }
852

    
853
                public void internalFrameClosed(InternalFrameEvent e) {
854
                    fireActionPerformed(e);
855
                }
856

    
857
                public void internalFrameClosing(InternalFrameEvent e) {
858
                    fireActionPerformed(e);
859
                }
860

    
861
                public void internalFrameDeactivated(InternalFrameEvent e) {
862
                    fireActionPerformed(e);
863
                }
864

    
865
                public void internalFrameDeiconified(InternalFrameEvent e) {
866
                    fireActionPerformed(e);
867
                }
868

    
869
                public void internalFrameIconified(InternalFrameEvent e) {
870
                    fireActionPerformed(e);
871
                }
872

    
873
                public void internalFrameOpened(InternalFrameEvent e) {
874
                    fireActionPerformed(e);
875
                }
876
            };
877
    }
878

    
879
    /**
880
     * Returns a Timer that fires once, after the delay. The delay can be restarted
881
     * by restarting the Timer.
882
     */
883
    public static Timer createRestartableSingleEventTimer(int delay,
884
        ActionListener listener) {
885
        Timer timer = new Timer(delay, listener);
886
        timer.setCoalesce(true);
887
        timer.setInitialDelay(delay);
888
        timer.setRepeats(false);
889

    
890
        return timer;
891
    }
892

    
893
    public static ValidatingTextField createSyncdTextField(JSlider s) {
894
        int columns = (int) Math.ceil(Math.log(s.getMaximum()) / Math.log(10));
895

    
896
        return createSyncdTextField(s, columns);
897
    }
898

    
899
    public static ValidatingTextField createSyncdTextField(JSlider s,
900
        int columns) {
901
        ValidatingTextField t = new ValidatingTextField(s.getValue() + "",
902
                columns, SwingConstants.RIGHT,
903
                ValidatingTextField.INTEGER_VALIDATOR,
904
                new ValidatingTextField.CompositeCleaner(new ValidatingTextField.Cleaner[] {
905
                        new ValidatingTextField.BlankCleaner("" +
906
                            s.getMinimum()),
907
                        new ValidatingTextField.MinIntCleaner(s.getMinimum()),
908
                        new ValidatingTextField.MaxIntCleaner(s.getMaximum())
909
                    }));
910
        sync(s, t);
911
        syncEnabledStates(s, t);
912

    
913
        return t;
914
    }
915
    
916
    /**
917
     * @see #createSyncdTextField(JSlider s, int columns)
918
     */
919
     public static void sync(final JSlider s, final ValidatingTextField t) {
920
        t.setText("" + s.getValue());
921

    
922
        final Boolean[] changing = new Boolean[] { Boolean.FALSE };
923
        s.addChangeListener(new ChangeListener() {
924
                public void stateChanged(ChangeEvent e) {
925
                    if (changing[0] == Boolean.TRUE) {
926
                        return;
927
                    }
928

    
929
                    changing[0] = Boolean.TRUE;
930

    
931
                    try {
932
                        t.setText("" + s.getValue());
933
                    } finally {
934
                        changing[0] = Boolean.FALSE;
935
                    }
936
                }
937
            });
938
        t.getDocument().addDocumentListener(new DocumentListener() {
939
                private void changed() {
940
                    if (changing[0] == Boolean.TRUE) {
941
                        return;
942
                    }
943

    
944
                    changing[0] = Boolean.TRUE;
945

    
946
                    try {
947
                        s.setValue(t.getInteger());
948
                    } finally {
949
                        changing[0] = Boolean.FALSE;
950
                    }
951
                }
952

    
953
                public void changedUpdate(DocumentEvent e) {
954
                    changed();
955
                }
956

    
957
                public void insertUpdate(DocumentEvent e) {
958
                    changed();
959
                }
960

    
961
                public void removeUpdate(DocumentEvent e) {
962
                    changed();
963
                }
964
            });
965
    }
966

    
967
    public static void syncEnabledStates(final JComponent c1,
968
        final JComponent c2) {
969
        c2.setEnabled(c1.isEnabled());
970
        c1.addPropertyChangeListener("enabled",
971
            new PropertyChangeListener() {
972
                public void propertyChange(PropertyChangeEvent evt) {
973
                    if (c1.isEnabled() == c2.isEnabled()) {
974
                        return;
975
                    }
976

    
977
                    c2.setEnabled(c1.isEnabled());
978
                }
979
            });
980
        c2.addPropertyChangeListener("enabled",
981
            new PropertyChangeListener() {
982
                public void propertyChange(PropertyChangeEvent evt) {
983
                    if (c1.isEnabled() == c2.isEnabled()) {
984
                        return;
985
                    }
986

    
987
                    c1.setEnabled(c2.isEnabled());
988
                }
989
            });
990
    }
991

    
992
    public static void sync(final JSlider s1, final JSlider s2) {
993
        s2.setValue(s1.getValue());
994
        Assert.isTrue(s1.getMinimum() == s2.getMinimum());
995
        Assert.isTrue(s1.getMaximum() == s2.getMaximum());
996

    
997
        final Boolean[] changing = new Boolean[] { Boolean.FALSE };
998
        s1.addChangeListener(new ChangeListener() {
999
                public void stateChanged(ChangeEvent e) {
1000
                    if (changing[0] == Boolean.TRUE) {
1001
                        return;
1002
                    }
1003

    
1004
                    changing[0] = Boolean.TRUE;
1005

    
1006
                    try {
1007
                        s2.setValue(s1.getValue());
1008
                    } finally {
1009
                        changing[0] = Boolean.FALSE;
1010
                    }
1011
                }
1012
            });
1013
        s2.addChangeListener(new ChangeListener() {
1014
                public void stateChanged(ChangeEvent e) {
1015
                    if (changing[0] == Boolean.TRUE) {
1016
                        return;
1017
                    }
1018

    
1019
                    changing[0] = Boolean.TRUE;
1020

    
1021
                    try {
1022
                        s1.setValue(s2.getValue());
1023
                    } finally {
1024
                        changing[0] = Boolean.FALSE;
1025
                    }
1026
                }
1027
            });
1028
    }
1029

    
1030
    public static void sync(final JCheckBox c1, final JCheckBox c2) {
1031
        c2.setSelected(c1.isSelected());
1032

    
1033
        final Boolean[] changing = new Boolean[] { Boolean.FALSE };
1034
        c1.addActionListener(new ActionListener() {
1035
                public void actionPerformed(ActionEvent e) {
1036
                    if (changing[0] == Boolean.TRUE) {
1037
                        return;
1038
                    }
1039

    
1040
                    changing[0] = Boolean.TRUE;
1041

    
1042
                    try {
1043
                        c2.setSelected(c1.isSelected());
1044
                    } finally {
1045
                        changing[0] = Boolean.FALSE;
1046
                    }
1047
                }
1048
            });
1049
        c2.addActionListener(new ActionListener() {
1050
                public void actionPerformed(ActionEvent e) {
1051
                    if (changing[0] == Boolean.TRUE) {
1052
                        return;
1053
                    }
1054

    
1055
                    changing[0] = Boolean.TRUE;
1056

    
1057
                    try {
1058
                        c1.setSelected(c2.isSelected());
1059
                    } finally {
1060
                        changing[0] = Boolean.FALSE;
1061
                    }
1062
                }
1063
            });
1064
    }
1065

    
1066
    public static List items(JComboBox comboBox) {
1067
        ArrayList items = new ArrayList();
1068

    
1069
        for (int i = 0; i < comboBox.getItemCount(); i++) {
1070
            items.add(comboBox.getItemAt(i));
1071
        }
1072

    
1073
        return items;
1074
    }
1075

    
1076
    /**
1077
     * Calls #doClick so that events are fired.
1078
     */
1079
    public static void setSelectedWithClick(JCheckBox checkBox, boolean selected) {
1080
        checkBox.setSelected(!selected);
1081
        checkBox.doClick();
1082
    }
1083

    
1084
    public static void setLocation(Component componentToMove,
1085
        Location location, Component other) {
1086
        Point p = new Point((int) other.getLocationOnScreen().getX() +
1087
                (location.fromRight
1088
                ? (other.getWidth() - componentToMove.getWidth() - location.x)
1089
                : location.x),
1090
                (int) other.getLocationOnScreen().getY() +
1091
                (location.fromBottom
1092
                ? (other.getHeight() - componentToMove.getHeight() -
1093
                location.y) : location.y));
1094
        SwingUtilities.convertPointFromScreen(p, componentToMove.getParent());
1095
        componentToMove.setLocation(p);
1096
    }
1097

    
1098

    
1099
    /** Highlights a given component with a given color. 
1100
     * Great for GridBagLayout debugging. 
1101
     *
1102
     * @author Jon Aquino
1103
     */
1104
    public static void highlightForDebugging(JComponent component, Color color) {
1105
        component.setBackground(color);
1106
        component.setBorder(BorderFactory.createMatteBorder(10, 10, 10, 10,
1107
                color));
1108
    }
1109

    
1110
    public static Component topCard(Container c) {
1111
        Assert.isTrue(c.getLayout() instanceof CardLayout);
1112

    
1113
        Component[] components = c.getComponents();
1114

    
1115
        for (int i = 0; i < components.length; i++) {
1116
            if (components[i].isVisible()) {
1117
                return components[i];
1118
            }
1119
        }
1120

    
1121
        Assert.shouldNeverReachHere();
1122

    
1123
        return null;
1124
    }
1125

    
1126
    /**
1127
     * Work around Java Bug 4437688 "JFileChooser.getSelectedFile() returns
1128
     * nothing when a file is selected" [Jon Aquino]
1129
     */
1130
    public static File[] selectedFiles(JFileChooser chooser) {
1131
        return ((chooser.getSelectedFiles().length == 0) &&
1132
        (chooser.getSelectedFile() != null))
1133
        ? new File[] { chooser.getSelectedFile() } : chooser.getSelectedFiles();
1134
    }
1135

    
1136
    public static ImageIcon toDisabledIcon(ImageIcon icon) {
1137
        return new ImageIcon(GrayFilter.createDisabledImage((icon).getImage()));
1138
    }
1139

    
1140
    public static Component getDescendantOfClass(Class c, Container container) {
1141
        for (int i = 0; i < container.getComponentCount(); i++) {
1142
            if (c.isInstance(container.getComponent(i))) {
1143
                return container.getComponent(i);
1144
            }
1145

    
1146
            if (container.getComponent(i) instanceof Container) {
1147
                Component descendant = getDescendantOfClass(c,
1148
                        (Container) container.getComponent(i));
1149

    
1150
                if (descendant != null) {
1151
                    return descendant;
1152
                }
1153
            }
1154
        }
1155

    
1156
        return null;
1157
    }
1158

    
1159
    /**
1160
     * Ensures that the next frame is activated when #dispose is called
1161
     * explicitly, in JDK 1.4. JDK 1.3 didn't have this problem.
1162
     */
1163
    public static void dispose(final JInternalFrame internalFrame,
1164
        JDesktopPane desktopPane) {
1165
        desktopPane.getDesktopManager().closeFrame(internalFrame);
1166
        internalFrame.dispose();
1167
    }
1168

    
1169
    private static class Int {
1170
        public volatile int i;
1171
    }
1172

    
1173
    public static class Location {
1174
        private int x;
1175
        private int y;
1176
        private boolean fromRight;
1177
        private boolean fromBottom;
1178

    
1179
        /**
1180
         * Constructor taking an initial location, offset hint.
1181
         *
1182
         * @param fromBottom whether y is the number of pixels between the bottom
1183
         * edges of the toolbox and desktop pane, or between the top edges.
1184
         */
1185
        public Location(int x, boolean fromRight, int y, boolean fromBottom) {
1186
            this.x = x;
1187
            this.y = y;
1188
            this.fromRight = fromRight;
1189
            this.fromBottom = fromBottom;
1190
        }
1191
    }
1192
}