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