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