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.ui;
030    
031    import java.awt.BorderLayout;
032    import java.awt.Component;
033    import java.awt.event.ActionEvent;
034    import java.awt.event.ActionListener;
035    import java.awt.event.MouseAdapter;
036    import java.awt.event.MouseEvent;
037    import java.lang.reflect.InvocationTargetException;
038    import java.net.URL;
039    import java.util.ArrayList;
040    import java.util.List;
041    
042    import javax.swing.ImageIcon;
043    import javax.swing.JComponent;
044    import javax.swing.JMenuItem;
045    import javax.swing.JOptionPane;
046    import javax.swing.JPanel;
047    import javax.swing.JPopupMenu;
048    import javax.swing.SwingUtilities;
049    import javax.swing.border.BevelBorder;
050    
051    import org.w3c.dom.Document;
052    import org.w3c.dom.Element;
053    
054    import ucar.unidata.idv.IdvResourceManager;
055    import ucar.unidata.idv.IntegratedDataViewer;
056    import ucar.unidata.idv.MapViewManager;
057    import ucar.unidata.idv.TransectViewManager;
058    import ucar.unidata.idv.ViewDescriptor;
059    import ucar.unidata.idv.ViewManager;
060    import ucar.unidata.idv.control.DisplayControlImpl;
061    import ucar.unidata.idv.ui.IdvComponentGroup;
062    import ucar.unidata.idv.ui.IdvComponentHolder;
063    import ucar.unidata.idv.ui.IdvUIManager;
064    import ucar.unidata.idv.ui.IdvWindow;
065    import ucar.unidata.ui.ComponentHolder;
066    import ucar.unidata.util.GuiUtils;
067    import ucar.unidata.util.LayoutUtil;
068    import ucar.unidata.util.LogUtil;
069    import ucar.unidata.util.Msg;
070    import ucar.unidata.xml.XmlResourceCollection;
071    import ucar.unidata.xml.XmlUtil;
072    
073    import edu.wisc.ssec.mcidasv.PersistenceManager;
074    
075    /**
076     * Extends the IDV component groups so that we can intercept clicks for Bruce's
077     * tab popup menu and handle drag and drop. It also intercepts ViewManager
078     * creation in order to wrap components in McIDASVComponentHolders rather than
079     * IdvComponentHolders. Doing this allows us to associate ViewManagers back to
080     * their ComponentHolders, and this functionality is taken advantage of to form
081     * the hierarchical names seen in the McIDASVViewPanel.
082     */
083    public class McvComponentGroup extends IdvComponentGroup {
084    
085        /** Path to the "close tab" icon in the popup menu. */
086        protected static final String ICO_CLOSE =
087            "/edu/wisc/ssec/mcidasv/resources/icons/tabmenu/stop-loads16.png";
088    
089        /** Path to the "rename" icon in the popup menu. */
090        protected static final String ICO_RENAME =
091            "/edu/wisc/ssec/mcidasv/resources/icons/tabmenu/accessories-text-editor16.png";
092    
093        /** Path to the eject icon in the popup menu. */
094        protected static final String ICO_UNDOCK =
095            "/edu/wisc/ssec/mcidasv/resources/icons/tabmenu/media-eject16.png";
096    
097        /** Action command for destroying a display. */
098        private static final String CMD_DISPLAY_DESTROY = "DESTROY_DISPLAY_TAB";
099    
100        /** Action command for ejecting a display from a tab. */
101        private static final String CMD_DISPLAY_EJECT = "EJECT_TAB";
102    
103        /** Action command for renaming a display. */
104        private static final String CMD_DISPLAY_RENAME = "RENAME_DISPLAY";
105    
106        /** The popup menu for the McV tabbed display interface. */
107        private final JPopupMenu popup = doMakeTabMenu();
108    
109        /** Number of tabs that have been stored in this group. */
110        @SuppressWarnings("unused")
111        private int tabCount = 0;
112    
113        /** Whether or not <code>init</code> has been called. */
114        private boolean initDone = false;
115    
116        /**
117         * Holders that McV knows are held by this component group. Used to avoid
118         * any needless work in <code>redoLayout</code>.
119         */
120        private List<ComponentHolder> knownHolders =
121            new ArrayList<ComponentHolder>();
122    
123        /** Keep a reference to avoid extraneous calls to <tt>getIdv().</tt> */
124        private IntegratedDataViewer idv;
125    
126        /** Reference to the window associated with this group. */
127        private IdvWindow window = IdvWindow.getActiveWindow();
128    
129        /** 
130         * Whether or not {@link #redoLayout()} needs to worry about a renamed 
131         * tab. 
132         */
133        private boolean tabRenamed = false;
134    
135        /**
136         * Default constructor for serialization.
137         */
138        public McvComponentGroup() {}
139    
140        /**
141         * A pretty typical constructor.
142         * 
143         * @param idv The main IDV instance.
144         * @param name Presumably the name of this component group?
145         */
146        public McvComponentGroup(final IntegratedDataViewer idv, 
147            final String name) 
148        {
149            super(idv, name);
150            this.idv = idv;
151            init();
152        }
153    
154        /**
155         * This constructor catches the window that will be contain this group.
156         * 
157         * @param idv The main IDV instance.
158         * @param name Presumably the name of this component group?
159         * @param window The window holding this component group.
160         */
161        public McvComponentGroup(final IntegratedDataViewer idv,
162            final String name, final IdvWindow window) 
163        {
164            super(idv, name);
165            this.window = window;
166            this.idv = idv;
167            init();
168        }
169    
170        /**
171         * Initializes the various UI components.
172         */
173        private void init() {
174            if (initDone) {
175                return;
176            }
177            tabbedPane = new DraggableTabbedPane(window, idv, this);
178    //        tabbedPane.addMouseListener(new TabPopupListener());
179    
180            container = new JPanel(new BorderLayout());
181            container.add(tabbedPane);
182    //        container.addComponentListener(new ComponentListener() {
183    //            @Override public void componentHidden(ComponentEvent e) {
184    //                
185    //            }
186    //            @Override public void componentShown(ComponentEvent e) {
187    //                
188    //            }
189    //            @Override public void componentMoved(ComponentEvent e) {}
190    //            @Override public void componentResized(ComponentEvent e) {}
191    //        });
192            GuiUtils.handleHeavyWeightComponentsInTabs(tabbedPane);
193            initDone = true;
194        }
195    
196        /**
197         * Create and return the GUI contents. Overridden so that McV can implement
198         * the right click tab menu and draggable tabs.
199         * 
200         * @return GUI contents
201         */
202        @Override public JComponent doMakeContents() {
203            redoLayout();
204            outerContainer = LayoutUtil.center(container);
205            outerContainer.validate();
206            return outerContainer;
207        }
208    
209        /**
210         * <p>
211         * Importing a display control entails adding the control to the component
212         * group and informing the UI that the control is no longer in its own
213         * window.
214         * </p>
215         * 
216         * <p>
217         * Overridden in McV so that the display control is wrapped in a
218         * McIDASVComponentHolder rather than a IdvComponentHolder.
219         * </p>
220         * 
221         * @param dc The display control to import.
222         */
223        @Override public void importDisplayControl(final DisplayControlImpl dc) {
224            if (dc.getComponentHolder() != null) {
225                dc.getComponentHolder().removeDisplayControl(dc);
226            }
227            idv.getIdvUIManager().getViewPanel().removeDisplayControl(dc);
228            dc.guiImported();
229            addComponent(new McvComponentHolder(idv, dc));
230        }
231    
232        /**
233         * Basically just creates a McVCompHolder for holding a dynamic skin and
234         * sets the name of the component holder.
235         * 
236         * @param root The XML skin that we'll use.
237         */
238        public void makeDynamicSkin(final Element root) {
239            IdvComponentHolder comp =
240                new McvComponentHolder(idv, XmlUtil.toString(root));
241    
242            comp.setType(McvComponentHolder.TYPE_DYNAMIC_SKIN);
243            comp.setName("Dynamic Skin Test");
244            addComponent(comp);
245            comp.doMakeContents();
246        }
247    
248        /**
249         * Doesn't do anything for the time being...
250         * 
251         * @param doc
252         * 
253         * @return XML representation of the contents of this component group.
254         */
255        @Override public Element createXmlNode(final Document doc) {
256            // System.err.println("caught createXmlNode");
257            Element e = super.createXmlNode(doc);
258            // System.err.println(XmlUtil.toString(e));
259            // System.err.println("exit createXmlNode");
260            return e;
261        }
262    
263        /**
264         * <p>
265         * Handles creation of the component represented by the XML skin at the
266         * given index.
267         * </p>
268         * 
269         * <p>
270         * Overridden so that McV can wrap the component in a
271         * McIDASVComponentHolder.
272         * </p>
273         * 
274         * @param index The index of the skin within the skin resource.
275         */
276        @Override public void makeSkin(final int index) {
277    //        final XmlResourceCollection skins = idv.getResourceManager().getXmlResources(
278    //            IdvResourceManager.RSC_SKIN);
279    //
280    ////        String id = skins.getProperty("skinid", index);
281    ////        if (id == null)
282    ////            id = skins.get(index).toString();
283    //
284    ////        SwingUtilities.invokeLater(new Runnable() {
285    ////            public void run() {
286    //                String id = skins.getProperty("skinid", index);
287    //                if (id == null)
288    //                    id = skins.get(index).toString();
289    //                IdvComponentHolder comp = new McvComponentHolder(idv, id);
290    //                comp.setType(IdvComponentHolder.TYPE_SKIN);
291    //                comp.setName("untitled");
292    //
293    //                addComponent(comp);
294    ////            }
295    ////        });
296            makeSkinAtIndex(index);
297        }
298        
299        public IdvComponentHolder makeSkinAtIndex(final int index) {
300            final XmlResourceCollection skins = idv.getResourceManager().getXmlResources(
301                            IdvResourceManager.RSC_SKIN);
302            String id = skins.getProperty("skinid", index);
303            if (id == null) {
304                id = skins.get(index).toString();
305            }
306            IdvComponentHolder comp = new McvComponentHolder(idv, id);
307            comp.setType(IdvComponentHolder.TYPE_SKIN);
308            comp.setName("untitled");
309    
310            addComponent(comp);
311            return comp;
312        }
313    
314        /**
315         * <p>
316         * Create a new component whose type will be determined by the contents of
317         * <code>what</code>.
318         * </p>
319         * 
320         * <p>
321         * Overridden so that McV can wrap up the components in
322         * McVComponentHolders, which allow McV to map ViewManagers to
323         * ComponentHolders.
324         * </p>
325         * 
326         * @param what String that determines what sort of component we create.
327         */
328        @Override public void makeNew(final String what) {
329            try {
330                ViewManager vm = null;
331                ComponentHolder comp = null;
332                String property = "showControlLegend=false";
333                ViewDescriptor desc = new ViewDescriptor();
334    
335                // we're only really interested in map, globe, or transect views.
336                if (what.equals(IdvUIManager.COMP_MAPVIEW)) {
337                    vm = new MapViewManager(idv, desc, property);
338                } else if (what.equals(IdvUIManager.COMP_TRANSECTVIEW)) {
339                    vm = new TransectViewManager(idv, desc, property);
340                } else if (what.equals(IdvUIManager.COMP_GLOBEVIEW)) {
341                    vm = new MapViewManager(idv, desc, property);
342                    ((MapViewManager)vm).setUseGlobeDisplay(true);
343                } else {
344                    // hand off uninteresting things to the IDV
345                    super.makeNew(what);
346                    return;
347                }
348    
349                // make sure we get the component into a mcv component holder,
350                // otherwise we won't be able to easily map ViewManagers to
351                // ComponentHolders for the hierarchical names in the ViewPanel.
352                idv.getVMManager().addViewManager(vm);
353                comp = new McvComponentHolder(idv, vm);
354    
355                if (comp != null) {
356                    addComponent(comp);
357    //                GuiUtils.showComponentInTabs(comp.getContents());
358                }
359    
360            } catch (Exception exc) {
361                LogUtil.logException("Error making new " + what, exc);
362            }
363        }
364    
365        /**
366         * <p>
367         * Forces this group to layout its components. Extended because the IDV was
368         * doing extra work that McIDAS-V doesn't need, such as dealing with
369         * layouts other than LAYOUT_TABS and needlessly reinitializing the group's
370         * container.
371         * </p>
372         * 
373         * @see ucar.unidata.ui.ComponentGroup#redoLayout()
374         */
375        @SuppressWarnings("unchecked")
376        @Override public void redoLayout() {
377            final List<ComponentHolder> currentHolders = getDisplayComponents();
378            if (!tabRenamed && knownHolders.equals(currentHolders)) {
379                return;
380            }
381    
382            if (tabbedPane == null) {
383                return;
384            }
385    
386            Runnable updateGui = new Runnable() {
387                public void run() {
388                    int selectedIndex = tabbedPane.getSelectedIndex();
389    
390                    tabbedPane.setVisible(false);
391                    tabbedPane.removeAll();
392    
393                    knownHolders = new ArrayList<ComponentHolder>(currentHolders);
394                    for (ComponentHolder holder : knownHolders) {
395                        tabbedPane.addTab(holder.getName(), holder.getContents());
396                    }
397    
398                    if (tabRenamed) {
399                        tabbedPane.setSelectedIndex(selectedIndex);
400                    }
401    
402                    tabbedPane.setVisible(true);
403                    tabRenamed = false;
404                }
405            };
406            
407            if (SwingUtilities.isEventDispatchThread()) {
408                SwingUtilities.invokeLater(updateGui);
409            } else {
410                try {
411                    SwingUtilities.invokeAndWait(updateGui);
412                } catch (InterruptedException e) {
413                    // TODO Auto-generated catch block
414                    e.printStackTrace();
415                } catch (InvocationTargetException e) {
416                    // TODO Auto-generated catch block
417                    e.printStackTrace();
418                }
419            }
420        }
421    
422        // TODO(jon): remove this method if Unidata implements your fix.
423        @Override public void getViewManagers(@SuppressWarnings("rawtypes") final List viewManagers) {
424            if ((viewManagers == null) || (getDisplayComponents() == null)) {
425    //            logger.debug("McvComponentGroup.getViewManagers(): bailing out early!");
426                return;
427            }
428    
429            super.getViewManagers(viewManagers);
430        }
431    
432        /**
433         * <p>
434         * Adds a component holder to this group. Extended so that the added holder
435         * becomes the active tab, and the component is explicitly set to visible
436         * in an effort to fix that heavyweight/lightweight component problem.
437         * </p>
438         * 
439         * @param holder
440         * @param index
441         * 
442         * @see ucar.unidata.ui.ComponentGroup#addComponent(ComponentHolder, int)
443         */
444        @Override public void addComponent(final ComponentHolder holder,
445            final int index) 
446        {
447            if (shouldGenerateName(holder, index)) {
448                holder.setName("untitled");
449            }
450    
451            if (holder.getName().trim().length() == 0) {
452                holder.setName("untitled");
453            }
454    
455            super.addComponent(holder, index);
456            setActiveComponentHolder(holder);
457            holder.getContents().setVisible(true);
458    
459            if (window != null) {
460                window.setTitle(makeWindowTitle(holder.getName()));
461            }
462        }
463    
464        private boolean shouldGenerateName(final ComponentHolder h, final int i) {
465            if (h.getName() != null && !h.getName().startsWith("untitled")) {
466                return false;
467            }
468    
469            boolean invalidIndex = (i >= 0);
470            boolean withoutName = (h.getName() == null || h.getName().length() == 0);
471            boolean loadingBundle = ((PersistenceManager)getIdv().getPersistenceManager()).isBundleLoading();
472    
473            return invalidIndex || withoutName || !loadingBundle;
474        }
475    
476        /**
477         * Used to set the tab associated with {@code holder} as the active tab 
478         * in our {@link JTabbedPane}.
479         * 
480         * @param holder The active component holder.
481         */
482        public void setActiveComponentHolder(final ComponentHolder holder) {
483            if (getDisplayComponentCount() > 1) {
484                final int newIdx = getDisplayComponents().indexOf(holder);
485                SwingUtilities.invokeLater(new Runnable() {
486                    public void run() {
487                        setActiveIndex(newIdx);
488                    }
489                });
490                
491            }
492    
493            // TODO: this doesn't work quite right...
494            if (window == null) {
495                window = IdvWindow.getActiveWindow();
496            }
497            if (window != null) {
498    //            SwingUtilities.invokeLater(new Runnable() {
499    //                public void run() {
500                        window.toFront();
501    //                  window.setTitle(holder.getName());
502                        window.setTitle(makeWindowTitle(holder.getName()));
503    //                }
504    //            });
505            }
506        }
507    
508        /**
509         * @return The index of the active component holder within this group.
510         */
511        public int getActiveIndex() {
512            if (tabbedPane == null) {
513                return -1;
514            } else {
515                return tabbedPane.getSelectedIndex();
516            }
517        }
518    
519        /**
520         * Make the component holder at {@code index} active.
521         * 
522         * @param index The index of the desired component holder.
523         * 
524         * @return True if the active component holder was set, false otherwise.
525         */
526        public boolean setActiveIndex(final int index) {
527            int size = getDisplayComponentCount();
528            if ((index < 0) || (index >= size)) {
529                return false;
530            }
531    
532    //        SwingUtilities.invokeLater(new Runnable() {
533    //            public void run() {
534                    tabbedPane.setSelectedIndex(index);
535                    if (window != null) {
536                        ComponentHolder h = (ComponentHolder)getDisplayComponents().get(index);
537                        if (h != null) {
538                            window.setTitle(makeWindowTitle(h.getName()));
539                        }
540                    }
541    //            }
542    //        });
543            return true;
544        }
545    
546        /**
547         * Returns the index of {@code holder} within this component group.
548         * 
549         * @return Either the index of {@code holder}, or {@code -1} 
550         * if {@link #getDisplayComponents()} returns a {@code null} {@link List}.
551         * 
552         * @see List#indexOf(Object)
553         */
554        @Override public int indexOf(final ComponentHolder holder) {
555            @SuppressWarnings("rawtypes")
556            List dispComps = getDisplayComponents();
557            if (dispComps == null) {
558                return -1;
559            } else {
560                return getDisplayComponents().indexOf(holder);
561            }
562        }
563    
564        /**
565         * Returns the {@link ComponentHolder} at the given position within this
566         * component group. 
567         * 
568         * @param index Index of the {@code ComponentHolder} to return.
569         * 
570         * @return {@code ComponentHolder} at {@code index}.
571         * 
572         * @see List#get(int)
573         */
574        protected ComponentHolder getHolderAt(final int index) {
575            @SuppressWarnings("unchecked")
576            List<ComponentHolder> dispComps = getDisplayComponents();
577            return dispComps.get(index);
578        }
579    
580        /**
581         * @return Component holder that corresponds to the selected tab.
582         */
583        public ComponentHolder getActiveComponentHolder() {
584            int idx = 0;
585    
586            if (getDisplayComponentCount() > 1) {
587    //            idx = tabbedPane.getSelectedIndex();
588                idx = getActiveIndex();
589            }
590    
591    //        return (ComponentHolder)getDisplayComponents().get(idx);
592            return getHolderAt(idx);
593        }
594    
595        /**
596         * Overridden so that McV can also update its copy of the IDV reference.
597         */
598        @Override public void setIdv(final IntegratedDataViewer newIdv) {
599            super.setIdv(newIdv);
600            idv = newIdv;
601        }
602    
603        /**
604         * Create a window title suitable for an application window.
605         * 
606         * @param title Window title
607         * 
608         * @return Application title plus the window title.
609         */
610        private String makeWindowTitle(final String title) {
611            String defaultApplicationName = "McIDAS-V";
612            if (idv != null) {
613                defaultApplicationName = idv.getStateManager().getTitle();
614            }
615            return UIManager.makeTitle(defaultApplicationName, title);
616        }
617    
618        /**
619         * Returns the number of display components {@literal "in"} this group.
620         * 
621         * @return Either the {@code size()} of the {@link List} returned by 
622         * {@link #getDisplayComponents()} or {@code -1} if 
623         * {@code getDisplayComponents()} returns a {@code null} {@code List}.
624         */
625        protected int getDisplayComponentCount() {
626            @SuppressWarnings("rawtypes")
627            List dispComps = getDisplayComponents();
628            if (dispComps == null) {
629                return -1;
630            } else {
631                return dispComps.size();
632            }
633        }
634        
635        /**
636         * Create the <tt>JPopupMenu</tt> that will be displayed for a tab.
637         * 
638         * @return Menu initialized with tab options
639         */
640        protected JPopupMenu doMakeTabMenu() {
641            ActionListener menuListener = new ActionListener() {
642                public void actionPerformed(ActionEvent evt) {
643                    final String cmd = evt.getActionCommand();
644                    if (CMD_DISPLAY_EJECT.equals(cmd)) {
645                        ejectDisplay(tabbedPane.getSelectedIndex());
646                    } else if (CMD_DISPLAY_RENAME.equals(cmd)) {
647                        renameDisplay(tabbedPane.getSelectedIndex());
648                    } else if (CMD_DISPLAY_DESTROY.equals(cmd)) {
649                        destroyDisplay(tabbedPane.getSelectedIndex());
650                    }
651                }
652            };
653    
654            final JPopupMenu popup = new JPopupMenu();
655            JMenuItem item;
656    
657            // URL img = getClass().getResource(ICO_UNDOCK);
658            // item = new JMenuItem("Undock", new ImageIcon(img));
659            // item.setActionCommand(CMD_DISPLAY_EJECT);
660            // item.addActionListener(menuListener);
661            // popup.add(item);
662    
663            URL img = getClass().getResource(ICO_RENAME);
664            item = new JMenuItem("Rename", new ImageIcon(img));
665            item.setActionCommand(CMD_DISPLAY_RENAME);
666            item.addActionListener(menuListener);
667            popup.add(item);
668    
669            // popup.addSeparator();
670    
671            img = getClass().getResource(ICO_CLOSE);
672            item = new JMenuItem("Close", new ImageIcon(img));
673            item.setActionCommand(CMD_DISPLAY_DESTROY);
674            item.addActionListener(menuListener);
675            popup.add(item);
676    
677            popup.setBorder(new BevelBorder(BevelBorder.RAISED));
678    
679            Msg.translateTree(popup);
680            return popup;
681        }
682    
683        /**
684         * Remove the component holder at index {@code idx}. This method does
685         * not destroy the component holder.
686         * 
687         * @param idx Index of the ejected component holder.
688         * 
689         * @return Component holder that was ejected.
690         */
691        private ComponentHolder ejectDisplay(final int idx) {
692            return null;
693        }
694    
695        /**
696         * Prompt the user to change the name of the component holder at index
697         * {@code idx}. Nothing happens if the user doesn't enter anything.
698         * 
699         * @param idx Index of the component holder.
700         */
701        protected void renameDisplay(final int idx) {
702            final String title =
703                JOptionPane.showInputDialog(
704                    IdvWindow.getActiveWindow().getFrame(), "Enter new name",
705                    makeWindowTitle("Rename Tab"), JOptionPane.PLAIN_MESSAGE);
706    
707            if (title == null) {
708                return;
709            }
710    
711    //        final List<ComponentHolder> comps = getDisplayComponents();
712    //        comps.get(idx).setName(title);
713            getHolderAt(idx).setName(title);
714            tabRenamed = true;
715            if (window != null) {
716                window.setTitle(makeWindowTitle(title));
717            }
718            redoLayout();
719        }
720    
721        /**
722         * Prompts the user to confirm removal of the component holder at index
723         * {@code idx}. Nothing happens if the user declines.
724         * 
725         * @param idx Index of the component holder.
726         * 
727         * @return Either {@code true} if the user elected to remove, 
728         * {@code false} otherwise.
729         */
730        protected boolean destroyDisplay(final int idx) {
731    //        final List<IdvComponentHolder> comps = getDisplayComponents();
732    //        IdvComponentHolder comp = comps.get(idx);
733            return ((IdvComponentHolder)getHolderAt(idx)).removeDisplayComponent();
734    //        return comp.removeDisplayComponent();
735        }
736    
737        /**
738         * Remove the component at {@code index} without forcing the IDV-land
739         * component group to redraw.
740         * 
741         * @param index The index of the component to be removed.
742         * 
743         * @return The removed component.
744         */
745        @SuppressWarnings("unchecked")
746        public ComponentHolder quietRemoveComponentAt(final int index) {
747            List<ComponentHolder> comps = getDisplayComponents();
748            if (comps == null || comps.size() == 0) {
749                return null;
750            }
751            ComponentHolder removed = comps.remove(index);
752            removed.setParent(null);
753            return removed;
754        }
755    
756        /**
757         * Adds a component to the end of the list of display components without
758         * forcing the IDV-land code to redraw.
759         * 
760         * @param component The component to add.
761         * 
762         * @return The index of the newly added component, or {@code -1} if 
763         * {@link #getDisplayComponents()} returned a null {@code List}.
764         */
765        @SuppressWarnings("unchecked")
766        public int quietAddComponent(final ComponentHolder component) {
767            List<ComponentHolder> comps = getDisplayComponents();
768            if (comps == null) {
769                return -1;
770            }
771            if (comps.contains(component)) {
772                comps.remove(component);
773            }
774            comps.add(component);
775            component.setParent(this);
776            return comps.indexOf(component);
777        }
778    
779        /**
780         * Handle pop-up events for tabs.
781         */
782        @SuppressWarnings("unused")
783        private class TabPopupListener extends MouseAdapter {
784    
785            @Override public void mouseClicked(final MouseEvent evt) {
786                checkPopup(evt);
787            }
788    
789            @Override public void mousePressed(final MouseEvent evt) {
790                checkPopup(evt);
791            }
792    
793            @Override public void mouseReleased(final MouseEvent evt) {
794                checkPopup(evt);
795            }
796    
797            /**
798             * <p>
799             * Determines whether or not the tab popup menu should be shown, and
800             * if so, which parts of it should be enabled or disabled.
801             * </p>
802             * 
803             * @param evt Allows us to determine the type of event.
804             */
805            private void checkPopup(final MouseEvent evt) {
806                if (evt.isPopupTrigger()) {
807                    // can't close or eject last tab
808                    // TODO: re-evaluate this
809                    Component[] comps = popup.getComponents();
810                    for (Component comp : comps) {
811                        if (comp instanceof JMenuItem) {
812                            String cmd = ((JMenuItem)comp).getActionCommand();
813                            if ((CMD_DISPLAY_DESTROY.equals(cmd) || CMD_DISPLAY_EJECT.equals(cmd))
814                                && tabbedPane.getTabCount() == 1) {
815                                comp.setEnabled(false);
816                            } else {
817                                comp.setEnabled(true);
818                            }
819                        }
820                    }
821                    popup.show(tabbedPane, evt.getX(), evt.getY());
822                }
823            }
824        }
825    }