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