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