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 }