001/*
002 * $Id: McVGuiUtils.java,v 1.43 2011/03/24 16:06:35 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2011
007 * Space Science and Engineering Center (SSEC)
008 * University of Wisconsin - Madison
009 * 1225 W. Dayton Street, Madison, WI 53706, USA
010 * https://www.ssec.wisc.edu/mcidas
011 * 
012 * All Rights Reserved
013 * 
014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015 * some McIDAS-V source code is based on IDV and VisAD source code.  
016 * 
017 * McIDAS-V is free software; you can redistribute it and/or modify
018 * it under the terms of the GNU Lesser Public License as published by
019 * the Free Software Foundation; either version 3 of the License, or
020 * (at your option) any later version.
021 * 
022 * McIDAS-V is distributed in the hope that it will be useful,
023 * but WITHOUT ANY WARRANTY; without even the implied warranty of
024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
025 * GNU Lesser Public License for more details.
026 * 
027 * You should have received a copy of the GNU Lesser Public License
028 * along with this program.  If not, see http://www.gnu.org/licenses.
029 */
030
031package edu.wisc.ssec.mcidasv.util;
032
033import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.arrList;
034import static javax.swing.GroupLayout.DEFAULT_SIZE;
035import static javax.swing.GroupLayout.PREFERRED_SIZE;
036import static javax.swing.GroupLayout.Alignment.BASELINE;
037import static javax.swing.GroupLayout.Alignment.LEADING;
038import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;
039
040import java.awt.Color;
041import java.awt.Component;
042import java.awt.Dimension;
043import java.awt.Font;
044import java.awt.Graphics;
045import java.awt.Image;
046import java.awt.Rectangle;
047import java.awt.event.HierarchyEvent;
048import java.util.ArrayList;
049import java.util.Collection;
050import java.util.Collections;
051import java.util.EventObject;
052import java.util.List;
053import java.util.Map;
054import java.util.Map.Entry;
055import java.util.regex.Pattern;
056
057import javax.swing.GroupLayout;
058import javax.swing.GroupLayout.ParallelGroup;
059import javax.swing.GroupLayout.SequentialGroup;
060import javax.swing.ImageIcon;
061import javax.swing.JButton;
062import javax.swing.JComboBox;
063import javax.swing.JComponent;
064import javax.swing.JLabel;
065import javax.swing.JMenuItem;
066import javax.swing.JPanel;
067import javax.swing.JTextField;
068import javax.swing.SwingConstants;
069import javax.swing.SwingUtilities;
070
071import ucar.unidata.idv.MapViewManager;
072import ucar.unidata.idv.ViewManager;
073import ucar.unidata.idv.ui.IdvComponentGroup;
074import ucar.unidata.idv.ui.IdvComponentHolder;
075import ucar.unidata.idv.ui.IdvWindow;
076import ucar.unidata.idv.ui.WindowInfo;
077import ucar.unidata.ui.ComponentHolder;
078import ucar.unidata.ui.MultiFrame;
079import ucar.unidata.util.GuiUtils;
080
081import edu.wisc.ssec.mcidasv.Constants;
082import edu.wisc.ssec.mcidasv.ui.McvComponentGroup;
083import edu.wisc.ssec.mcidasv.ui.McvComponentHolder;
084
085
086public class McVGuiUtils implements Constants {
087    private McVGuiUtils() {}
088    
089    public enum Width { HALF, SINGLE, ONEHALF, DOUBLE, TRIPLE, QUADRUPLE, DOUBLEDOUBLE }
090    public enum Position { LEFT, RIGHT, CENTER }
091    public enum Prefer { TOP, BOTTOM, NEITHER }
092    public enum TextColor { NORMAL, STATUS }
093    
094    /**
095     * Use this class to create a panel with a background image
096     * @author davep
097     *
098     */
099    public static class IconPanel extends JPanel {
100        private Image img;
101
102        public IconPanel(String img) {
103                this(GuiUtils.getImageIcon(img).getImage());
104        }
105
106        public IconPanel(Image img) {
107                this.img = img;
108                Dimension size = new Dimension(img.getWidth(null), img.getHeight(null));
109                setPreferredSize(size);
110                setMinimumSize(size);
111                setMaximumSize(size);
112                setSize(size);
113                setLayout(null);
114        }
115
116        public void paintComponent(Graphics g) {
117            super.paintComponent(g);
118                g.drawImage(img, 0, 0, null);
119        }
120
121    }
122
123    /**
124     * Create a standard sized, right-justified label
125     * @param title
126     * @return
127     */
128    public static JLabel makeLabelRight(String title) {
129        return makeLabelRight(title, null);
130    }
131    
132    public static JLabel makeLabelRight(String title, Width width) {
133        if (width==null) width=Width.SINGLE;
134        JLabel newLabel = new JLabel(title);
135        setComponentWidth(newLabel, width);
136        setLabelPosition(newLabel, Position.RIGHT);
137        return newLabel;
138    }
139
140    /**
141     * Create a standard sized, left-justified label
142     * @param title
143     * @return
144     */
145    public static JLabel makeLabelLeft(String title) {
146        return makeLabelLeft(title, null);
147    }
148    
149    public static JLabel makeLabelLeft(String title, Width width) {
150        if (width==null) width=Width.SINGLE;
151        JLabel newLabel = new JLabel(title);
152        setComponentWidth(newLabel, width);
153        setLabelPosition(newLabel, Position.LEFT);
154        return newLabel;
155    }
156
157    /**
158     * Create a sized, labeled component
159     * @param label
160     * @param thing
161     * @return
162     */
163    public static JPanel makeLabeledComponent(String label, JComponent thing) {
164        return makeLabeledComponent(makeLabelRight(label), thing);
165    }
166    
167    public static JPanel makeLabeledComponent(JLabel label, JComponent thing) {
168        return makeLabeledComponent(label, thing, Position.RIGHT);
169    }
170
171    public static JPanel makeLabeledComponent(String label, JComponent thing, Position position) {
172        return makeLabeledComponent(new JLabel(label), thing, position);
173    }
174    
175    public static JPanel makeLabeledComponent(JLabel label, JComponent thing, Position position) {
176        JPanel newPanel = new JPanel();
177
178        if (position == Position.RIGHT) {
179                setComponentWidth(label);
180                setLabelPosition(label, Position.RIGHT);
181        }
182        
183        GroupLayout layout = new GroupLayout(newPanel);
184        newPanel.setLayout(layout);
185        layout.setHorizontalGroup(
186                        layout.createParallelGroup(LEADING)
187                        .addGroup(layout.createSequentialGroup()
188                                        .addComponent(label)
189                                        .addGap(GAP_RELATED)
190                                        .addComponent(thing))
191        );
192        layout.setVerticalGroup(
193                        layout.createParallelGroup(LEADING)
194                        .addGroup(layout.createParallelGroup(BASELINE)
195                                        .addComponent(label)
196                                        .addComponent(thing))
197        );
198
199        return newPanel;
200    }
201    
202    /**
203     * Create a sized, labeled component
204     * @param label
205     * @param thing
206     * @return
207     */
208    public static JPanel makeComponentLabeled(JComponent thing, String label) {
209        return makeComponentLabeled(thing, new JLabel(label));
210    }
211    
212    public static JPanel makeComponentLabeled(JComponent thing, String label, Position position) {
213        return makeComponentLabeled(thing, new JLabel(label), position);
214    }
215    
216    public static JPanel makeComponentLabeled(JComponent thing, JLabel label) {
217        return makeComponentLabeled(thing, label, Position.LEFT);
218    }
219
220    public static JPanel makeComponentLabeled(JComponent thing, JLabel label, Position position) {
221        JPanel newPanel = new JPanel();
222
223        if (position == Position.RIGHT) {
224                setComponentWidth(label);
225                setLabelPosition(label, Position.RIGHT);
226        }
227        
228        GroupLayout layout = new GroupLayout(newPanel);
229        newPanel.setLayout(layout);
230        layout.setHorizontalGroup(
231                        layout.createParallelGroup(LEADING)
232                        .addGroup(layout.createSequentialGroup()
233                                        .addComponent(thing)
234                                        .addGap(GAP_RELATED)
235                                        .addComponent(label))
236        );
237        layout.setVerticalGroup(
238                        layout.createParallelGroup(LEADING)
239                        .addGroup(layout.createParallelGroup(BASELINE)
240                                        .addComponent(thing)
241                                        .addComponent(label))
242        );
243
244        return newPanel;
245    }
246            
247    /**
248     * Set the width of an existing component
249     * @param existingComponent
250     */
251    public static void setComponentWidth(JComponent existingComponent) {
252        setComponentWidth(existingComponent, Width.SINGLE);
253    }
254
255    public static void setComponentWidth(JComponent existingComponent, Width width) {
256        if (width == null)
257            width = Width.SINGLE;
258
259        switch (width) {
260        case HALF:
261                setComponentWidth(existingComponent, ELEMENT_HALF_WIDTH);
262                break;
263
264        case SINGLE: 
265                setComponentWidth(existingComponent, ELEMENT_WIDTH);
266                break;
267
268        case ONEHALF:           
269                setComponentWidth(existingComponent, ELEMENT_ONEHALF_WIDTH);
270                break;
271                
272        case DOUBLE: 
273                setComponentWidth(existingComponent, ELEMENT_DOUBLE_WIDTH);
274                break;
275
276        case TRIPLE: 
277                setComponentWidth(existingComponent, ELEMENT_DOUBLE_WIDTH + ELEMENT_WIDTH);
278                break;
279
280        case QUADRUPLE: 
281                setComponentWidth(existingComponent, ELEMENT_DOUBLE_WIDTH + ELEMENT_DOUBLE_WIDTH);
282                break;
283
284        case DOUBLEDOUBLE: 
285                setComponentWidth(existingComponent, ELEMENT_DOUBLEDOUBLE_WIDTH);
286                break;
287
288        default:                        
289                setComponentWidth(existingComponent, ELEMENT_WIDTH);
290                break;
291        }
292    }
293    
294    /**
295     * Set the width of an existing component to a given int width
296     * @param existingComponent
297     * @param width
298     */
299    public static void setComponentWidth(JComponent existingComponent, int width) {
300                existingComponent.setMinimumSize(new Dimension(width, 24));
301                existingComponent.setMaximumSize(new Dimension(width, 24));
302                existingComponent.setPreferredSize(new Dimension(width, 24));
303    }
304    
305    /**
306     * Set the component width to that of another component
307     */
308    public static void setComponentWidth(JComponent setme, JComponent getme) {
309        setComponentWidth(setme, getme, 0);
310    }
311    
312    public static void setComponentWidth(JComponent setme, JComponent getme, int padding) {
313        setme.setPreferredSize(new Dimension(getme.getPreferredSize().width + padding, getme.getPreferredSize().height));
314    }
315    
316    /**
317     * Set the component height to that of another component
318     */
319    public static void setComponentHeight(JComponent setme, JComponent getme) {
320        setComponentHeight(setme, getme, 0);
321    }
322    
323    public static void setComponentHeight(JComponent setme, JComponent getme, int padding) {
324        setme.setPreferredSize(new Dimension(getme.getPreferredSize().width, getme.getPreferredSize().height + padding));
325    }
326
327    /**
328     * Set the label position of an existing label
329     * @param existingLabel
330     */
331    public static void setLabelPosition(JLabel existingLabel) {
332        setLabelPosition(existingLabel, Position.LEFT);
333    }
334    
335    public static void setLabelPosition(JLabel existingLabel, Position position) {
336        switch (position) {
337        case LEFT:
338                existingLabel.setHorizontalTextPosition(SwingConstants.LEFT);
339                existingLabel.setHorizontalAlignment(SwingConstants.LEFT);
340                break;
341
342        case RIGHT: 
343                existingLabel.setHorizontalTextPosition(SwingConstants.RIGHT);
344                existingLabel.setHorizontalAlignment(SwingConstants.RIGHT);
345                break;
346
347        case CENTER:            
348                existingLabel.setHorizontalTextPosition(SwingConstants.CENTER);
349                existingLabel.setHorizontalAlignment(SwingConstants.CENTER);
350                break;
351
352        default:                        
353                existingLabel.setHorizontalTextPosition(SwingConstants.LEFT);
354                existingLabel.setHorizontalAlignment(SwingConstants.LEFT);
355        break;
356        }
357    }
358    
359    /**
360     * Set the bold attribute of an existing label
361     * @param existingLabel
362     * @param bold
363     */
364    public static void setLabelBold(JLabel existingLabel, boolean bold) {
365        Font f = existingLabel.getFont();
366        if (bold)
367                existingLabel.setFont(f.deriveFont(f.getStyle() ^ Font.BOLD));
368        else
369                existingLabel.setFont(f.deriveFont(f.getStyle() | Font.BOLD));
370    }
371    
372    /**
373     * Set the foreground color of an existing component
374     * @param existingComponent
375     */
376    public static void setComponentColor(JComponent existingComponent) {
377        setComponentColor(existingComponent, TextColor.NORMAL);
378    }
379    
380    public static void setComponentColor(JComponent existingComponent, TextColor color) {
381        switch (color) {
382        case NORMAL:
383                existingComponent.setForeground(new Color(0, 0, 0));
384                break;
385
386        case STATUS: 
387                existingComponent.setForeground(MCV_BLUE_DARK);
388                break;
389                
390        default:                        
391                existingComponent.setForeground(new Color(0, 0, 0));
392                break;
393        }
394    }
395
396    /**
397     * Custom makeImageButton to ensure proper sizing and mouseborder are set
398     */
399    public static JButton makeImageButton(String iconName, 
400                final Object object,
401                final String methodName,
402                final Object arg,
403                final String tooltip
404    ) {
405
406        final JButton btn = makeImageButton(iconName, tooltip);
407        return (JButton) GuiUtils.addActionListener(btn, object, methodName, arg);
408    }
409
410    /**
411     * Custom makeImageButton to ensure proper sizing and mouseborder are set
412     */
413    public static JButton makeImageButton(String iconName, String tooltip) {
414        boolean addMouseOverBorder = true;
415
416        ImageIcon imageIcon = GuiUtils.getImageIcon(iconName);
417        if (imageIcon.getIconWidth() > 22 || imageIcon.getIconHeight() > 22) {
418                Image scaledImage  = imageIcon.getImage().getScaledInstance(22, 22, Image.SCALE_SMOOTH);
419                imageIcon = new ImageIcon(scaledImage);
420        }
421
422        final JButton btn = GuiUtils.getImageButton(imageIcon);
423        btn.setBackground(null);
424        btn.setContentAreaFilled(false);
425        btn.setSize(new Dimension(24, 24));
426        btn.setPreferredSize(new Dimension(24, 24));
427        btn.setMinimumSize(new Dimension(24, 24));
428        if (addMouseOverBorder) {
429                GuiUtils.makeMouseOverBorder(btn);
430        }
431        btn.setToolTipText(tooltip);
432        return btn;
433    }
434    
435    /**
436     * Create a button with text and an icon
437     */
438    public static JButton makeImageTextButton(String iconName, String label) {
439        JButton newButton = new JButton(label);
440        setButtonImage(newButton, iconName);
441        return newButton;
442    }
443    
444    /**
445     * Add an icon to a button... but only if the LookAndFeel supports it
446     */
447    public static void setButtonImage(JButton existingButton, String iconName) {
448        // TODO: see if this is fixed in some future Apple Java release?
449        // When using Aqua look and feel don't use icons in the buttons
450        // Messes with the button vertical sizing
451        if (existingButton.getBorder().toString().indexOf("Aqua") > 0) return;
452        ImageIcon imageIcon = GuiUtils.getImageIcon(iconName);
453        existingButton.setIcon(imageIcon);
454    }
455
456    /**
457     * Add an icon to a menu item
458     */
459    public static void setMenuImage(JMenuItem existingMenuItem, String iconName) {
460        ImageIcon imageIcon = GuiUtils.getImageIcon(iconName);
461        existingMenuItem.setIcon(imageIcon);
462    }
463
464    public static <E> JComboBox makeComboBox(final E[] items, final Object selected) {
465        return makeComboBox(CollectionHelpers.list(items), selected);
466    }
467
468    public static <E> JComboBox makeComboBox(final E[] items, final Object selected, final Width width) {
469        return makeComboBox(CollectionHelpers.list(items), selected, width);
470    }
471
472    public static JComboBox makeComboBox(final Collection<?> items, final Object selected) {
473        return makeComboBox(items, selected, null);
474    }
475
476    public static JComboBox makeComboBox(final Collection<?> items, final Object selected, final Width width) {
477        JComboBox newComboBox = getEditableBox(items, selected);
478        setComponentWidth(newComboBox, width);
479        return newComboBox;
480    }
481
482    public static void setListData(final JComboBox box, final Collection<?> items, final Object selected) {
483        box.removeAllItems();
484        if (items != null) {
485            for (Object o : items)
486                box.addItem(o);
487        }
488
489        if (selected != null && !items.contains(selected))
490            box.addItem(selected);
491    }
492
493    public static JComboBox getEditableBox(final Collection<?> items, final Object selected) {
494        JComboBox fld = new JComboBox();
495        fld.setEditable(true);
496        setListData(fld, items, selected);
497        if (selected != null) {
498            fld.setSelectedItem(selected);
499        }
500        return fld;
501    }
502
503    /**
504     * Create a standard sized text field
505     * @param value
506     * @return
507     */
508    public static JTextField makeTextField(String value) {
509        return makeTextField(value, null);
510    }
511
512    public static JTextField makeTextField(String value, Width width) {
513        JTextField newTextField = new McVTextField(value);
514        setComponentWidth(newTextField, width);
515        return newTextField;
516    }
517
518    /**
519     * Create some custom text entry widgets
520     */
521    public static McVTextField makeTextFieldLimit(String defaultString, int limit) {
522        return new McVTextField(defaultString, limit);
523    }
524    
525    public static McVTextField makeTextFieldUpper(String defaultString, int limit) {
526        return new McVTextField(defaultString, limit, true);
527    }
528    
529    public static McVTextField makeTextFieldAllow(String defaultString, int limit, boolean upper, String allow) {
530        McVTextField newField = new McVTextField(defaultString, limit, upper);
531        newField.setAllow(allow);
532        return newField;
533    }
534    
535    public static McVTextField makeTextFieldDeny(String defaultString, int limit, boolean upper, String deny) {
536        McVTextField newField = new McVTextField(defaultString, limit, upper);
537        newField.setDeny(deny);
538        return newField;
539    }
540        
541    public static McVTextField makeTextFieldAllow(String defaultString, int limit, boolean upper, char[] allow) {
542        McVTextField newField = new McVTextField(defaultString, limit, upper);
543        newField.setAllow(allow);
544        return newField;
545    }
546    
547    public static McVTextField makeTextFieldDeny(String defaultString, int limit, boolean upper, char[] deny) {
548        McVTextField newField = new McVTextField(defaultString, limit, upper);
549        newField.setDeny(deny);
550        return newField;
551    }
552    
553    public static McVTextField makeTextFieldAllow(String defaultString, int limit, boolean upper, Pattern allow) {
554        McVTextField newField = new McVTextField(defaultString, limit, upper);
555        newField.setAllow(allow);
556        return newField;
557    }
558    
559    public static McVTextField makeTextFieldDeny(String defaultString, int limit, boolean upper, Pattern deny) {
560        McVTextField newField = new McVTextField(defaultString, limit, upper);
561        newField.setDeny(deny);
562        return newField;
563    }
564    
565    /**
566     * Use GroupLayout for stacking components vertically
567     * Set center to resize vertically
568     * @param top
569     * @param center
570     * @param bottom
571     * @return
572     */
573    public static JPanel topCenterBottom(JComponent top, JComponent center, JComponent bottom) {
574        JPanel newPanel = new JPanel();
575                
576        GroupLayout layout = new GroupLayout(newPanel);
577        newPanel.setLayout(layout);
578        layout.setHorizontalGroup(
579            layout.createParallelGroup(LEADING)
580            .addComponent(top, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
581            .addComponent(center, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
582            .addComponent(bottom, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
583        );
584        layout.setVerticalGroup(
585            layout.createParallelGroup(LEADING)
586            .addGroup(layout.createSequentialGroup()
587                .addComponent(top, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)
588                .addPreferredGap(RELATED)
589                .addComponent(center, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
590                .addPreferredGap(RELATED)
591                .addComponent(bottom, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE))
592        );
593
594        return newPanel;
595    }
596
597    /**
598     * Use GroupLayout for stacking components vertically
599     * @param top
600     * @param bottom
601     * @param which
602     * @return
603     */
604    public static JPanel topBottom(JComponent top, JComponent bottom, Prefer which) {
605        JPanel newPanel = new JPanel();
606
607        int topSize=PREFERRED_SIZE;
608        int bottomSize=PREFERRED_SIZE;
609        
610        if (which == Prefer.TOP) topSize = Short.MAX_VALUE;
611        else if (which == Prefer.BOTTOM) topSize = Short.MAX_VALUE;
612                
613        GroupLayout layout = new GroupLayout(newPanel);
614        newPanel.setLayout(layout);
615        layout.setHorizontalGroup(
616                        layout.createParallelGroup(LEADING)
617                        .addComponent(top, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
618                        .addComponent(bottom, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
619        );
620        layout.setVerticalGroup(
621                        layout.createParallelGroup(LEADING)
622                        .addGroup(layout.createSequentialGroup()
623                                        .addComponent(top, PREFERRED_SIZE, DEFAULT_SIZE, topSize)
624                                        .addPreferredGap(RELATED)
625                                        .addComponent(bottom, PREFERRED_SIZE, DEFAULT_SIZE, bottomSize))
626        );
627
628        return newPanel;
629    }
630    
631    /**
632     * Use GroupLayout for wrapping components to stop vertical resizing
633     * @param left
634     * @param right
635     * @return
636     */
637    public static JPanel sideBySide(JComponent left, JComponent right) {
638        return sideBySide(left, right, GAP_RELATED);
639    }
640    
641    /**
642     * Use GroupLayout for wrapping components to stop vertical resizing
643     * @param left
644     * @param right
645     * @return
646     */
647    public static JPanel sideBySide(JComponent left, JComponent right, int gap) {
648        JPanel newPanel = new JPanel();
649
650        GroupLayout layout = new GroupLayout(newPanel);
651        newPanel.setLayout(layout);
652        layout.setHorizontalGroup(
653                        layout.createParallelGroup(LEADING)
654                        .addGroup(layout.createSequentialGroup()
655                                        .addComponent(left, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)
656                                        .addGap(gap)
657                                        .addComponent(right, DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
658        );
659        layout.setVerticalGroup(
660                        layout.createParallelGroup(LEADING)
661                        .addGroup(layout.createSequentialGroup()
662                        .addGroup(layout.createParallelGroup(LEADING)
663                                        .addComponent(left, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)
664                                        .addComponent(right, PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE)))
665        );
666
667        return newPanel;
668    }
669    
670    /**
671     * Use GroupLayout for wrapping a list of components horizontally
672     */
673    public static JPanel horizontal(Component[] components) {
674        JPanel newPanel = new JPanel();
675        
676        GroupLayout layout = new GroupLayout(newPanel);
677        newPanel.setLayout(layout);
678        
679        SequentialGroup hGroup = layout.createSequentialGroup();
680        for (int i=0; i<components.length; i++) {
681                if (i>0) hGroup.addGap(GAP_RELATED);
682                hGroup.addComponent(components[i], DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE);
683        }
684
685        SequentialGroup vGroup = layout.createSequentialGroup();
686        ParallelGroup vInner = layout.createParallelGroup(LEADING);
687        for (int i=0; i<components.length; i++) {
688                vInner.addComponent(components[i], PREFERRED_SIZE, DEFAULT_SIZE, PREFERRED_SIZE);
689        }
690        vGroup.addGroup(vInner);
691
692        layout.setHorizontalGroup(
693                        layout.createParallelGroup(LEADING).addGroup(hGroup)
694        );
695        layout.setVerticalGroup(
696                        layout.createParallelGroup(LEADING).addGroup(vGroup)
697        );
698
699        return newPanel;
700    }
701
702    /**
703     * Use GroupLayout for wrapping a list of components vertically
704     */
705    public static JPanel vertical(Component[] components) {
706        JPanel newPanel = new JPanel();
707        
708        GroupLayout layout = new GroupLayout(newPanel);
709        newPanel.setLayout(layout);
710        
711        ParallelGroup hGroup = layout.createParallelGroup(LEADING);
712        for (int i=0; i<components.length; i++) {
713                hGroup.addComponent(components[i], DEFAULT_SIZE, DEFAULT_SIZE, Short.MAX_VALUE);
714        }
715
716        int vSize=PREFERRED_SIZE;
717
718        ParallelGroup vGroup = layout.createParallelGroup(LEADING);
719        SequentialGroup vInner = layout.createSequentialGroup();
720        for (int i=0; i<components.length; i++) {
721                if (i>0) vInner.addGap(GAP_RELATED);
722                if (i == components.length-1) vSize = Short.MAX_VALUE;
723                vInner.addComponent(components[i], PREFERRED_SIZE, DEFAULT_SIZE, vSize);
724        }
725        vGroup.addGroup(vInner);
726
727        layout.setHorizontalGroup(
728                        layout.createParallelGroup(LEADING).addGroup(hGroup)
729        );
730        layout.setVerticalGroup(
731                        layout.createParallelGroup(LEADING).addGroup(vGroup)
732        );      
733        
734        return newPanel;
735    }
736
737    /**
738     * Hack apart an IDV button panel and do a few things:
739     * - Reorder the buttons based on OS preference
740     *   Windows: OK on left
741     *   Mac: OK on right
742     * - Add icons when we understand the button name
743     * 
744     * TODO: Revisit this?  Could hamper GUI performance.  But it is niiice...
745     * 
746     * @param idvButtonPanel
747     * @return
748     */
749    public static JPanel makePrettyButtons(JPanel idvButtonPanel) {     
750        // These are the buttons we know about
751        JButton buttonOK = null;
752        JButton buttonApply = null;
753        JButton buttonCancel = null;
754        JButton buttonHelp = null;
755        JButton buttonNew = null;
756        JButton buttonReset = null;
757        JButton buttonYes = null;
758        JButton buttonNo = null;
759        
760        // These are the buttons we don't know about
761        List<JButton> buttonList = new ArrayList<JButton>();
762        
763        // First pull apart the panel and see if it looks like we expect
764        Component[] comps = idvButtonPanel.getComponents();
765        for (int i=0; i<comps.length; i++) {
766                if (!(comps[i] instanceof JButton)) continue;
767                JButton button = (JButton)comps[i];
768                if (button.getText().equals("OK")) {
769                        buttonOK = makePrettyButton(button);
770                }
771                else if (button.getText().equals("Apply")) {
772                        buttonApply = makePrettyButton(button);
773                }
774                else if (button.getText().equals("Cancel")) {
775                        buttonCancel = makePrettyButton(button);
776                }
777                else if (button.getText().equals("Help")) {
778                        buttonHelp = makePrettyButton(button);
779                }
780                else if (button.getText().equals("New")) {
781                        buttonNew = makePrettyButton(button);
782                }
783                else if (button.getText().equals("Reset")) {
784                        buttonReset = makePrettyButton(button);
785                }
786                else if (button.getText().equals("Yes")) {
787                        buttonYes = makePrettyButton(button);
788                }
789                else if (button.getText().equals("No")) {
790                        buttonNo = makePrettyButton(button);
791                }
792                else {
793                        buttonList.add(button);
794                }
795        }
796                
797        // If we are on a Mac, this is the order (right aligned)
798        // Help, New, Reset, No, Yes, Cancel, Apply, OK
799        if (System.getProperty("os.name").indexOf("Mac OS X") >= 0) {
800                JPanel newButtonPanel = new JPanel();
801                if (buttonHelp!=null) newButtonPanel.add(buttonHelp);
802                if (buttonNew!=null) newButtonPanel.add(buttonNew);
803                if (buttonReset!=null) newButtonPanel.add(buttonReset);
804                if (buttonNo!=null) newButtonPanel.add(buttonNo);
805                if (buttonYes!=null) newButtonPanel.add(buttonYes);
806                if (buttonCancel!=null) newButtonPanel.add(buttonCancel);
807                if (buttonApply!=null) newButtonPanel.add(buttonApply);
808                if (buttonOK!=null) newButtonPanel.add(buttonOK);
809                if (buttonList.size() > 0) 
810                        return GuiUtils.right(GuiUtils.hbox(GuiUtils.hbox(buttonList), newButtonPanel));
811                else
812                        return(GuiUtils.right(newButtonPanel));
813        }
814        
815        // If we are not on a Mac, this is the order (center aligned)
816        // OK, Apply, Cancel, Yes, No, Reset, New, Help
817        if (System.getProperty("os.name").indexOf("Mac OS X") < 0) {
818                JPanel newButtonPanel = new JPanel();
819                if (buttonOK!=null) newButtonPanel.add(buttonOK);
820                if (buttonApply!=null) newButtonPanel.add(buttonApply);
821                if (buttonCancel!=null) newButtonPanel.add(buttonCancel);
822                if (buttonYes!=null) newButtonPanel.add(buttonYes);
823                if (buttonNo!=null) newButtonPanel.add(buttonNo);
824                if (buttonReset!=null) newButtonPanel.add(buttonReset);
825                if (buttonNew!=null) newButtonPanel.add(buttonNew);
826                if (buttonHelp!=null) newButtonPanel.add(buttonHelp);
827                if (buttonList.size() > 0) 
828                        return GuiUtils.center(GuiUtils.hbox(GuiUtils.hbox(buttonList), newButtonPanel));
829                else
830                        return(GuiUtils.center(newButtonPanel));
831        }
832        
833        return idvButtonPanel;
834    }
835    
836    /**
837     * Take a list of buttons and make them pretty
838     * 
839     * @param list
840     * @return list
841     */
842    public static List makePrettyButtons(List buttonList) {
843        List newButtons = new ArrayList();
844        for (int i=0; i<buttonList.size(); i++) {
845                if (buttonList.get(i) instanceof JButton)
846                        newButtons.add(makePrettyButton((JButton)(buttonList.get(i))));
847                else
848                        newButtons.add(buttonList.get(i));
849        }
850        return newButtons;
851    }
852    
853    /**
854     * Convenience method to make a button based solely on its name
855     */
856    public static JButton makePrettyButton(String name) {
857        return makePrettyButton(new JButton(name));
858    }
859    
860    /**
861     * - Add icons when we understand the button name
862     * 
863     * @param button
864     * @return button
865     */
866    public static JButton makePrettyButton(JButton button) {                    
867        McVGuiUtils.setComponentWidth(button, Width.ONEHALF);
868        if (button.getText().equals("OK")) {
869                McVGuiUtils.setButtonImage(button, ICON_ACCEPT_SMALL);
870        }
871        else if (button.getText().equals("Apply")) {
872                McVGuiUtils.setButtonImage(button, ICON_APPLY_SMALL);
873        }
874        else if (button.getText().equals("Cancel")) {
875                McVGuiUtils.setButtonImage(button, ICON_CANCEL_SMALL);
876        }
877        else if (button.getText().equals("Help")) {
878                McVGuiUtils.setButtonImage(button, ICON_HELP_SMALL);
879        }
880        else if (button.getText().equals("New")) {
881                McVGuiUtils.setButtonImage(button, ICON_ADD_SMALL);
882        }
883        else if (button.getText().equals("Reset")) {
884                McVGuiUtils.setButtonImage(button, ICON_UNDO_SMALL);
885        }
886        else if (button.getText().equals("Yes")) {
887                McVGuiUtils.setButtonImage(button, ICON_ACCEPT_SMALL);
888        }
889        else if (button.getText().equals("No")) {
890                McVGuiUtils.setButtonImage(button, ICON_CANCEL_SMALL);
891        }
892        else if (button.getText().equals("Close")) {
893                McVGuiUtils.setButtonImage(button, ICON_CANCEL_SMALL);
894        }
895        else if (button.getText().equals("Previous")) {
896                McVGuiUtils.setButtonImage(button, ICON_PREVIOUS_SMALL);
897        }
898        else if (button.getText().equals("Next")) {
899                McVGuiUtils.setButtonImage(button, ICON_NEXT_SMALL);
900        }
901        else if (button.getText().equals("Random")) {
902                McVGuiUtils.setButtonImage(button, ICON_RANDOM_SMALL);
903        }
904        else if (button.getText().equals("Support Form")) {
905                McVGuiUtils.setButtonImage(button, ICON_SUPPORT_SMALL);
906        }
907        return button;
908    }
909        
910    /**
911     * Print the hierarchy of components
912     */
913    public static void printUIComponents(JComponent parent) {
914        printUIComponents(parent, 0, 0);
915    }
916    public static void printUIComponents(JComponent parent, int index, int depth) {
917        if (parent == null) {
918                System.err.println("McVGuiUtils.printUIComponents: null parent");
919                return;
920        }
921        Component[] children = parent.getComponents();
922        int childcount = children.length;
923        
924                String indent = "";
925                for (int d=0; d<depth; d++) {
926                        indent += "  ";
927                }
928                System.out.println(indent + index + ": " + parent);
929
930        if (childcount > 0) {
931                for (int c=0; c<childcount; c++) {
932                        if (children[c] instanceof JComponent) {
933                                printUIComponents((JComponent)children[c], c, depth+1);
934                        }
935                }
936        }
937    }
938
939    /**
940     * Calls {@link SwingUtilities#invokeLater(Runnable)} if the current thread
941     * is not the event dispatch thread. If this thread <b>is</b> the EDT,
942     * then call {@link Runnable#run()} for {@code r}.
943     * 
944     * <p>Remember, you <i>do not</i> want to execute long-running tasks in the
945     * event dispatch thread--it'll lock up the GUI.
946     * 
947     * @param r Code to run in the event dispatch thread. Cannot be {@code null}.
948     */
949    public static void runOnEDT(final Runnable r) {
950        if (SwingUtilities.isEventDispatchThread()) {
951            r.run();
952        } else {
953            SwingUtilities.invokeLater(r);
954        }
955    }
956    
957    public static List<ViewManager> getViewManagers(final WindowInfo info) {
958        List<ViewManager> vms = arrList();
959        for (IdvComponentHolder holder : getComponentHolders(info)) {
960            vms.addAll(holder.getViewManagers());
961        }
962        return vms;
963    }
964    
965    public static List<ViewManager> getViewManagers(final IdvWindow window) {
966        List<ViewManager> vms = arrList();
967        vms.addAll(window.getViewManagers());
968
969        for (IdvComponentHolder holder : getComponentHolders(window)) {
970            vms.addAll(holder.getViewManagers());
971        }
972        return vms;
973    }
974    
975    /**
976     * @return Whether or not {@code h} contains some UI component like
977     * the dashboard of field selector. Yes, it can happen!
978     */
979    public static boolean isUIHolder(final IdvComponentHolder h) {
980        if (h.getType().equals(McvComponentHolder.TYPE_DYNAMIC_SKIN))
981            return false;
982        return h.getViewManagers().isEmpty();
983    }
984
985    /**
986     * @return Whether or not {@code h} is a dynamic skin.
987     */
988    public static boolean isDynamicSkin(final IdvComponentHolder h) {
989        return (h.getType().equals(McvComponentHolder.TYPE_DYNAMIC_SKIN));
990    }
991
992    /**
993     * @return Whether or not {@code windows} has at least one dynamic
994     * skin.
995     */
996    public static boolean hasDynamicSkins(final List<WindowInfo> windows) {
997        for (WindowInfo window : windows)
998            for (IdvComponentHolder holder : getComponentHolders(window))
999                if (isDynamicSkin(holder))
1000                    return true;
1001        return false;
1002    }
1003
1004    /**
1005     * @return The component holders within <code>windowInfo</code>.
1006     * @see #getComponentHolders(IdvComponentGroup)
1007     */
1008    public static List<IdvComponentHolder> getComponentHolders(
1009        final WindowInfo windowInfo) {
1010        List<IdvComponentHolder> holders = arrList();
1011
1012        Collection<Object> comps =
1013            windowInfo.getPersistentComponents().values();
1014
1015        for (Object comp : comps) {
1016            if (!(comp instanceof IdvComponentGroup))
1017                continue;
1018
1019            holders.addAll(getComponentHolders((IdvComponentGroup)comp));
1020        }
1021        return holders;
1022    }
1023
1024    /**
1025     * @return The component holders within {@code idvWindow}.
1026     * @see #getComponentHolders(IdvComponentGroup)
1027     */
1028    public static List<IdvComponentHolder> getComponentHolders(
1029        final IdvWindow idvWindow) 
1030    {
1031        List<IdvComponentHolder> holders = arrList();
1032        for (IdvComponentGroup group : (List<IdvComponentGroup>)idvWindow.getComponentGroups())
1033            holders.addAll(getComponentHolders(group));
1034        return holders;
1035    }
1036
1037    /**
1038     * @return <b>Recursively</b> searches {@code group} to find any 
1039     * component holders.
1040     */
1041    public static List<IdvComponentHolder> getComponentHolders(
1042        final IdvComponentGroup group) 
1043    {
1044        List<IdvComponentHolder> holders = arrList();
1045        List<ComponentHolder> comps = group.getDisplayComponents();
1046        if (comps.isEmpty())
1047            return holders;
1048
1049        for (ComponentHolder comp : comps) {
1050            if (comp instanceof IdvComponentGroup)
1051                holders.addAll(getComponentHolders((IdvComponentGroup)comp));
1052            else if (comp instanceof IdvComponentHolder)
1053                holders.add((IdvComponentHolder)comp);
1054        }
1055
1056        return holders;
1057    }
1058
1059    /**
1060     * @return <b>Recursively</b> searches {@code group} for any nested
1061     * component groups.
1062     */
1063    public static List<IdvComponentGroup> getComponentGroups(
1064        final IdvComponentGroup group) 
1065    {
1066        List<IdvComponentGroup> groups = arrList();
1067        groups.add(group);
1068
1069        List<ComponentHolder> comps = group.getDisplayComponents();
1070        if (comps.isEmpty())
1071            return groups;
1072
1073        for (ComponentHolder comp : comps)
1074            if (comp instanceof IdvComponentGroup)
1075                groups.addAll(getComponentGroups((IdvComponentGroup)comp));
1076        return groups;
1077    }
1078
1079    /**
1080     * @return Component groups contained in {@code window}.
1081     * @see #getComponentGroups(IdvComponentGroup)
1082     */
1083    public static List<IdvComponentGroup> getComponentGroups(
1084        final WindowInfo window) 
1085    {
1086        Collection<Object> comps = window.getPersistentComponents().values();
1087        for (Object comp : comps)
1088            if (comp instanceof IdvComponentGroup)
1089                return getComponentGroups((IdvComponentGroup)comp);
1090        return arrList();
1091    }
1092
1093    /**
1094     * @return Component groups contained in {@code windows}.
1095     * @see #getComponentGroups(IdvComponentGroup)
1096     */
1097    public static List<IdvComponentGroup> getComponentGroups(
1098        final List<WindowInfo> windows) 
1099    {
1100        List<IdvComponentGroup> groups = arrList();
1101        for (WindowInfo window : windows)
1102            groups.addAll(getComponentGroups(window));
1103        return groups;
1104    }
1105
1106    /**
1107     * @return The component group within {@code window}.
1108     */
1109    public static IdvComponentGroup getComponentGroup(final IdvWindow window) {
1110        List<IdvComponentGroup> groups = window.getComponentGroups();
1111        if (!groups.isEmpty())
1112            return groups.get(0);
1113        return null;
1114    }
1115
1116    /**
1117     * @return Whether or not {@code group} contains any component
1118     *         groups.
1119     */
1120    public static boolean hasNestedGroups(final IdvComponentGroup group) {
1121        List<ComponentHolder> comps = group.getDisplayComponents();
1122        for (ComponentHolder comp : comps)
1123            if (comp instanceof IdvComponentGroup)
1124                return true;
1125        return false;
1126    }
1127
1128    /**
1129     * @return All active component holders in McIDAS-V.
1130     */
1131    // TODO: needs update for nested groups
1132    public static List<IdvComponentHolder> getAllComponentHolders() {
1133        List<IdvComponentHolder> holders = arrList();
1134        for (IdvComponentGroup g : getAllComponentGroups())
1135            holders.addAll(g.getDisplayComponents());
1136        return holders;
1137    }
1138
1139    /**
1140     * @return All active component groups in McIDAS-V.
1141     */
1142    // TODO: needs update for nested groups
1143    public static List<IdvComponentGroup> getAllComponentGroups() {
1144        List<IdvComponentGroup> groups = arrList();
1145        for (IdvWindow w : getAllDisplayWindows())
1146            groups.addAll(w.getComponentGroups());
1147        return groups;
1148    }
1149
1150    /**
1151     * @return All windows that contain at least one component group.
1152     */
1153    public static List<IdvWindow> getAllDisplayWindows() {
1154        List<IdvWindow> windows = arrList();
1155        for (IdvWindow w : (List<IdvWindow>)IdvWindow.getWindows())
1156            if (!w.getComponentGroups().isEmpty())
1157                windows.add(w);
1158        return windows;
1159    }
1160
1161    /**
1162     * @return The component holder positioned after the active component holder.
1163     */
1164    public static IdvComponentHolder getAfterActiveHolder() {
1165        return getAfterHolder(getActiveComponentHolder());
1166    }
1167
1168    /**
1169     * @return The component holder positioned before the active component holder.
1170     */
1171    public static IdvComponentHolder getBeforeActiveHolder() {
1172        return getBeforeHolder(getActiveComponentHolder());
1173    }
1174
1175    /**
1176     * @return The active component holder in the active window.
1177     */
1178    public static IdvComponentHolder getActiveComponentHolder() {
1179        IdvWindow window = IdvWindow.getActiveWindow();
1180        McvComponentGroup group = (McvComponentGroup)getComponentGroup(window);
1181        return (IdvComponentHolder)group.getActiveComponentHolder();
1182    }
1183
1184    /**
1185     * @return The component holder positioned after {@code current}.
1186     */
1187    public static IdvComponentHolder getAfterHolder(
1188        final IdvComponentHolder current) 
1189    {
1190        List<IdvComponentHolder> holders = getAllComponentHolders();
1191        int currentIndex = holders.indexOf(current);
1192        return holders.get( (currentIndex + 1) % holders.size());
1193    }
1194
1195    /**
1196     * @return The component holder positioned before {@code current}.
1197     */
1198    public static IdvComponentHolder getBeforeHolder(
1199        final IdvComponentHolder current) 
1200    {
1201        List<IdvComponentHolder> holders = getAllComponentHolders();
1202        int currentIndex = holders.indexOf(current);
1203        int newidx = (currentIndex - 1) % holders.size();
1204        if (newidx == -1) {
1205            newidx = holders.size() - 1;
1206        }
1207        return holders.get(newidx);
1208    }
1209
1210    /**
1211     * @param w {@link IdvWindow} whose component groups you want (as 
1212     * {@link McvComponentGroup}s).
1213     * 
1214     * @return A {@link List} of {@code McvComponentGroup}s or an empty list. 
1215     * If there were no {@code McvComponentGroup}s in {@code w}, 
1216     * <b>or</b> if {@code w} is {@code null}, an empty {@code List} is returned.
1217     */
1218    public static List<McvComponentGroup> idvGroupsToMcv(final IdvWindow w) {
1219        if (w == null) {
1220            return Collections.emptyList();
1221        }
1222        final List<McvComponentGroup> groups = arrList();
1223        for (IdvComponentGroup group : w.getComponentGroups()) {
1224            groups.add((McvComponentGroup)group);
1225        }
1226        return groups;
1227    }
1228
1229    public static void compGroup(final IdvComponentGroup g) {
1230        compGroup(g, 0);
1231    }
1232
1233    public static void compGroup(final IdvComponentGroup g, final int level) {
1234        p("Comp Group", level);
1235        p("  name=" + g.getName(), level);
1236        p("  id=" + g.getUniqueId(), level);
1237        p("  layout=" + g.getLayout(), level);
1238        p("  comp count=" + g.getDisplayComponents().size() + ": ", level);
1239        for (Object comp : g.getDisplayComponents()) {
1240            if (comp instanceof IdvComponentHolder) {
1241                compHolder((IdvComponentHolder)comp, level+1);
1242            } else if (comp instanceof IdvComponentGroup) {
1243                compGroup((IdvComponentGroup)comp, level+1);
1244            } else {
1245                p("    umm=" + comp.getClass().getName(), level);
1246            }
1247        }
1248    }
1249
1250    public static void compHolder(final IdvComponentHolder h, final int level) {
1251        p("Comp Holder", level);
1252        p("  cat=" + h.getCategory(), level);
1253        p("  name=" + h.getName(), level);
1254        p("  id=" + h.getUniqueId(), level);
1255        if (h.getViewManagers() == null) {
1256            System.err.println("  null vms!");
1257            return;
1258        }
1259        p("  vm count=" + h.getViewManagers().size() + ": ", level);
1260        for (ViewManager vm : (List<ViewManager>)h.getViewManagers()) {
1261            p("    " + vmType(vm) + "=" + vm.getViewDescriptor().getName(), level);
1262        }
1263    }
1264
1265    public static List<ViewManager> findvms(final List<WindowInfo> windows) {
1266        List<ViewManager> vms = new ArrayList<ViewManager>();
1267        for (WindowInfo window : windows) {
1268            for (IdvComponentHolder h : getComponentHolders(window)) {
1269                if (h.getViewManagers() != null) {
1270                    vms.addAll((List<ViewManager>)h.getViewManagers());
1271                } else {
1272                    System.err.println(h.getUniqueId() + " has no vms!");
1273                }
1274            }
1275        }
1276        for (ViewManager vm : vms) {
1277            System.err.println("vm=" + vm.getViewDescriptor().getName());
1278        }
1279        return vms;
1280    }
1281
1282    private static String vmType(final ViewManager vm) {
1283        if (vm instanceof MapViewManager) {
1284            if (((MapViewManager)vm).getUseGlobeDisplay()) {
1285                return "Globe";
1286            } else {
1287                return "Map";
1288            }
1289        }
1290        return "Other";
1291    }
1292
1293    private static String pad(final String str, final int pad) {
1294        char[] padding = new char[pad*2];
1295        for (int i = 0; i < pad*2; i++) {
1296            padding[i] = ' ';
1297        }
1298        return new String(padding).concat(str);
1299    }
1300
1301    private static void p(final String str, final int padding) {
1302        System.err.println(pad(str, padding));
1303    }
1304
1305    /**
1306     * Find the {@literal "bounds"} for the physical display at {@code index}.
1307     * 
1308     * @param index Zero-based index of the desired physical display.
1309     * 
1310     * @return Either a {@link java.awt.Rectangle} representing the display's
1311     * bounds, or {@code null} if {@code index} is invalid.
1312     */
1313    public static Rectangle getDisplayBoundsFor(final int index) {
1314        return SystemState.getDisplayBounds().get(index);
1315    }
1316
1317    /**
1318     * Tries to determine the physical display that contains the given
1319     * {@link java.awt.Rectangle}. <b>This method (currently) fails for 
1320     * {@code Rectangle}s that span multiple displays!</b>
1321     * 
1322     * @param rect {@code Rectangle} to test. Should not be {@code null}.
1323     * 
1324     * @return Either the (zero-based) index of the physical display, or 
1325     * {@code -1} if there was no match.
1326     */
1327    public static int findDisplayNumberForRectangle(final Rectangle rect) {
1328        Map<Integer, Rectangle> bounds = SystemState.getDisplayBounds();
1329        int index = -1;
1330        for (Entry<Integer, Rectangle> entry : bounds.entrySet()) {
1331            if (entry.getValue().contains(rect)) {
1332                index = entry.getKey();
1333                break;
1334            }
1335        }
1336        return index;
1337    }
1338
1339    /**
1340     * Tries to determine the physical display that contains the given
1341     * {@link java.awt.Component}. <b>This method (currently) fails for 
1342     * {@code Component}s that span multiple displays!</b>
1343     * 
1344     * @param comp {@code Component} to test. Should not be {@code null}.
1345     * 
1346     * @return Either the (zero-based) index of the physical display, or 
1347     * {@code -1} if there was no match.
1348     */
1349    public static int findDisplayNumberForComponent(final Component comp) {
1350        return findDisplayNumberForRectangle(
1351            new Rectangle(comp.getLocation(), comp.getSize()));
1352    }
1353
1354    /**
1355     * Tries to determine the physical display that contains the given
1356     * {@link ucar.unidata.ui.MultiFrame}. <b>This method (currently) fails 
1357     * for {@code MultiFrame}s that span multiple displays!</b>
1358     * 
1359     * @param mf {@code MultiFrame} to test. Should not be {@code null}.
1360     * 
1361     * @return Either the (zero-based) index of the physical display, or 
1362     * {@code -1} if there was no match.
1363     */
1364    public static int findDisplayNumberForMultiFrame(final MultiFrame mf) {
1365        return findDisplayNumberForRectangle(
1366            new Rectangle(mf.getLocation(), mf.getSize()));
1367    }
1368
1369    /**
1370     * Tries to determine the physical display that contains the rectangle 
1371     * defined by the specified coordinates. <b>This method (currently) fails 
1372     * for coordinates that span multiple displays!</b>
1373     * 
1374     * @param x X coordinate of the upper-left corner.
1375     * @param y Y coordinate of the upper-left corner.
1376     * @param width Width of the rectangle.
1377     * @param height Height of the rectangle.
1378     * 
1379     * @return Either the (zero-based) index of the physical display, or 
1380     * {@code -1} if there was no match.
1381     * 
1382     * @see java.awt.Rectangle#Rectangle(int, int, int, int)
1383     */
1384    public static int findDisplayNumberForCoords(final int x, final int y, 
1385        final int width, final int height) 
1386    {
1387        return findDisplayNumberForRectangle(
1388            new Rectangle(x, y, width, height));
1389    }
1390
1391    /**
1392     * Tries to determine which physical display contains the 
1393     * {@link java.awt.Component} or {@link ucar.unidata.ui.MultiFrame} that 
1394     * fired the given event. <b>This method (currently) fails for coordinates 
1395     * that span multiple displays!</b>
1396     * 
1397     * @param event {@code EventObject} to test. Should not be {@code null}.
1398     * 
1399     * @return Either the (zero-based) index of the physical display, or 
1400     * {@code -1} if there was no match.
1401     */
1402    public static int findDisplayNumberForEvent(final EventObject event) {
1403        int idx = -1;
1404        Object src = event.getSource();
1405        if (event instanceof HierarchyEvent) {
1406            src = ((HierarchyEvent)event).getChanged();
1407        }
1408        if (src != null) {
1409            if (src instanceof Component) {
1410                idx = findDisplayNumberForComponent((Component)src);
1411            } else if (src instanceof MultiFrame) {
1412                idx = findDisplayNumberForMultiFrame((MultiFrame)src);
1413            }
1414        }
1415        return idx;
1416    }
1417}