001/* 002 * $Id: McvComponentGroup.java,v 1.32 2011/03/24 18:13:11 davep Exp $ 003 * 004 * This file is part of McIDAS-V 005 * 006 * Copyright 2007-2011 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 031package edu.wisc.ssec.mcidasv.ui; 032 033import java.awt.BorderLayout; 034import java.awt.Component; 035import java.awt.event.ActionEvent; 036import java.awt.event.ActionListener; 037import java.awt.event.MouseAdapter; 038import java.awt.event.MouseEvent; 039import java.lang.reflect.InvocationTargetException; 040import java.net.URL; 041import java.util.ArrayList; 042import java.util.List; 043 044import javax.swing.ImageIcon; 045import javax.swing.JComponent; 046import javax.swing.JMenuItem; 047import javax.swing.JOptionPane; 048import javax.swing.JPanel; 049import javax.swing.JPopupMenu; 050import javax.swing.SwingUtilities; 051import javax.swing.border.BevelBorder; 052 053import org.w3c.dom.Document; 054import org.w3c.dom.Element; 055 056import ucar.unidata.idv.IdvResourceManager; 057import ucar.unidata.idv.IntegratedDataViewer; 058import ucar.unidata.idv.MapViewManager; 059import ucar.unidata.idv.TransectViewManager; 060import ucar.unidata.idv.ViewDescriptor; 061import ucar.unidata.idv.ViewManager; 062import ucar.unidata.idv.control.DisplayControlImpl; 063import ucar.unidata.idv.ui.IdvComponentGroup; 064import ucar.unidata.idv.ui.IdvComponentHolder; 065import ucar.unidata.idv.ui.IdvUIManager; 066import ucar.unidata.idv.ui.IdvWindow; 067import ucar.unidata.ui.ComponentHolder; 068import ucar.unidata.util.GuiUtils; 069import ucar.unidata.util.LayoutUtil; 070import ucar.unidata.util.LogUtil; 071import ucar.unidata.util.Msg; 072import ucar.unidata.xml.XmlResourceCollection; 073import ucar.unidata.xml.XmlUtil; 074 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 */ 085public 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 } 299 300 /** 301 * <p> 302 * Create a new component whose type will be determined by the contents of 303 * <code>what</code>. 304 * </p> 305 * 306 * <p> 307 * Overridden so that McV can wrap up the components in 308 * McVComponentHolders, which allow McV to map ViewManagers to 309 * ComponentHolders. 310 * </p> 311 * 312 * @param what String that determines what sort of component we create. 313 */ 314 @Override public void makeNew(final String what) { 315 try { 316 ViewManager vm = null; 317 ComponentHolder comp = null; 318 String property = "showControlLegend=false"; 319 ViewDescriptor desc = new ViewDescriptor(); 320 321 // we're only really interested in map, globe, or transect views. 322 if (what.equals(IdvUIManager.COMP_MAPVIEW)) { 323 vm = new MapViewManager(idv, desc, property); 324 } else if (what.equals(IdvUIManager.COMP_TRANSECTVIEW)) { 325 vm = new TransectViewManager(idv, desc, property); 326 } else if (what.equals(IdvUIManager.COMP_GLOBEVIEW)) { 327 vm = new MapViewManager(idv, desc, property); 328 ((MapViewManager)vm).setUseGlobeDisplay(true); 329 } else { 330 // hand off uninteresting things to the IDV 331 super.makeNew(what); 332 return; 333 } 334 335 // make sure we get the component into a mcv component holder, 336 // otherwise we won't be able to easily map ViewManagers to 337 // ComponentHolders for the hierarchical names in the ViewPanel. 338 idv.getVMManager().addViewManager(vm); 339 comp = new McvComponentHolder(idv, vm); 340 341 if (comp != null) { 342 addComponent(comp); 343// GuiUtils.showComponentInTabs(comp.getContents()); 344 } 345 346 } catch (Exception exc) { 347 LogUtil.logException("Error making new " + what, exc); 348 } 349 } 350 351 /** 352 * <p> 353 * Forces this group to layout its components. Extended because the IDV was 354 * doing extra work that McIDAS-V doesn't need, such as dealing with 355 * layouts other than LAYOUT_TABS and needlessly reinitializing the group's 356 * container. 357 * </p> 358 * 359 * @see ucar.unidata.ui.ComponentGroup#redoLayout() 360 */ 361 @SuppressWarnings("unchecked") 362 @Override public void redoLayout() { 363 final List<ComponentHolder> currentHolders = getDisplayComponents(); 364 if (!tabRenamed && knownHolders.equals(currentHolders)) { 365 return; 366 } 367 368 if (tabbedPane == null) { 369 return; 370 } 371 372 Runnable updateGui = new Runnable() { 373 public void run() { 374 int selectedIndex = tabbedPane.getSelectedIndex(); 375 376 tabbedPane.setVisible(false); 377 tabbedPane.removeAll(); 378 379 knownHolders = new ArrayList<ComponentHolder>(currentHolders); 380 for (ComponentHolder holder : knownHolders) { 381 tabbedPane.addTab(holder.getName(), holder.getContents()); 382 } 383 384 if (tabRenamed) { 385 tabbedPane.setSelectedIndex(selectedIndex); 386 } 387 388 tabbedPane.setVisible(true); 389 tabRenamed = false; 390 } 391 }; 392 393 if (SwingUtilities.isEventDispatchThread()) { 394 SwingUtilities.invokeLater(updateGui); 395 } else { 396 try { 397 SwingUtilities.invokeAndWait(updateGui); 398 } catch (InterruptedException e) { 399 // TODO Auto-generated catch block 400 e.printStackTrace(); 401 } catch (InvocationTargetException e) { 402 // TODO Auto-generated catch block 403 e.printStackTrace(); 404 } 405 } 406 } 407 408 // TODO(jon): remove this method if Unidata implements your fix. 409 @Override public void getViewManagers(@SuppressWarnings("rawtypes") final List viewManagers) { 410 if ((viewManagers == null) || (getDisplayComponents() == null)) { 411// logger.debug("McvComponentGroup.getViewManagers(): bailing out early!"); 412 return; 413 } 414 415 super.getViewManagers(viewManagers); 416 } 417 418 /** 419 * <p> 420 * Adds a component holder to this group. Extended so that the added holder 421 * becomes the active tab, and the component is explicitly set to visible 422 * in an effort to fix that heavyweight/lightweight component problem. 423 * </p> 424 * 425 * @param holder 426 * @param index 427 * 428 * @see ucar.unidata.ui.ComponentGroup#addComponent(ComponentHolder, int) 429 */ 430 @Override public void addComponent(final ComponentHolder holder, 431 final int index) 432 { 433 if (shouldGenerateName(holder, index)) { 434 holder.setName("untitled"); 435 } 436 437 if (holder.getName().trim().length() == 0) { 438 holder.setName("untitled"); 439 } 440 441 super.addComponent(holder, index); 442 setActiveComponentHolder(holder); 443 holder.getContents().setVisible(true); 444 445 if (window != null) { 446 window.setTitle(makeWindowTitle(holder.getName())); 447 } 448 } 449 450 private boolean shouldGenerateName(final ComponentHolder h, final int i) { 451 if (h.getName() != null && !h.getName().startsWith("untitled")) { 452 return false; 453 } 454 455 boolean invalidIndex = (i >= 0); 456 boolean withoutName = (h.getName() == null || h.getName().length() == 0); 457 boolean loadingBundle = ((PersistenceManager)getIdv().getPersistenceManager()).isBundleLoading(); 458 459 return invalidIndex || withoutName || !loadingBundle; 460 } 461 462 /** 463 * Used to set the tab associated with {@code holder} as the active tab 464 * in our {@link JTabbedPane}. 465 * 466 * @param holder The active component holder. 467 */ 468 public void setActiveComponentHolder(final ComponentHolder holder) { 469 if (getDisplayComponentCount() > 1) { 470 final int newIdx = getDisplayComponents().indexOf(holder); 471 SwingUtilities.invokeLater(new Runnable() { 472 public void run() { 473 setActiveIndex(newIdx); 474 } 475 }); 476 477 } 478 479 // TODO: this doesn't work quite right... 480 if (window == null) { 481 window = IdvWindow.getActiveWindow(); 482 } 483 if (window != null) { 484// SwingUtilities.invokeLater(new Runnable() { 485// public void run() { 486 window.toFront(); 487// window.setTitle(holder.getName()); 488 window.setTitle(makeWindowTitle(holder.getName())); 489// } 490// }); 491 } 492 } 493 494 /** 495 * @return The index of the active component holder within this group. 496 */ 497 public int getActiveIndex() { 498 if (tabbedPane == null) { 499 return -1; 500 } else { 501 return tabbedPane.getSelectedIndex(); 502 } 503 } 504 505 /** 506 * Make the component holder at {@code index} active. 507 * 508 * @param index The index of the desired component holder. 509 * 510 * @return True if the active component holder was set, false otherwise. 511 */ 512 public boolean setActiveIndex(final int index) { 513 int size = getDisplayComponentCount(); 514 if ((index < 0) || (index >= size)) { 515 return false; 516 } 517 518// SwingUtilities.invokeLater(new Runnable() { 519// public void run() { 520 tabbedPane.setSelectedIndex(index); 521 if (window != null) { 522 ComponentHolder h = (ComponentHolder)getDisplayComponents().get(index); 523 if (h != null) { 524 window.setTitle(makeWindowTitle(h.getName())); 525 } 526 } 527// } 528// }); 529 return true; 530 } 531 532 /** 533 * Returns the index of {@code holder} within this component group. 534 * 535 * @return Either the index of {@code holder}, or {@code -1} 536 * if {@link #getDisplayComponents()} returns a {@code null} {@link List}. 537 * 538 * @see List#indexOf(Object) 539 */ 540 @Override public int indexOf(final ComponentHolder holder) { 541 @SuppressWarnings("rawtypes") 542 List dispComps = getDisplayComponents(); 543 if (dispComps == null) { 544 return -1; 545 } else { 546 return getDisplayComponents().indexOf(holder); 547 } 548 } 549 550 /** 551 * Returns the {@link ComponentHolder} at the given position within this 552 * component group. 553 * 554 * @param index Index of the {@code ComponentHolder} to return. 555 * 556 * @return {@code ComponentHolder} at {@code index}. 557 * 558 * @see List#get(int) 559 */ 560 protected ComponentHolder getHolderAt(final int index) { 561 @SuppressWarnings("unchecked") 562 List<ComponentHolder> dispComps = getDisplayComponents(); 563 return dispComps.get(index); 564 } 565 566 /** 567 * @return Component holder that corresponds to the selected tab. 568 */ 569 public ComponentHolder getActiveComponentHolder() { 570 int idx = 0; 571 572 if (getDisplayComponentCount() > 1) { 573// idx = tabbedPane.getSelectedIndex(); 574 idx = getActiveIndex(); 575 } 576 577// return (ComponentHolder)getDisplayComponents().get(idx); 578 return getHolderAt(idx); 579 } 580 581 /** 582 * Overridden so that McV can also update its copy of the IDV reference. 583 */ 584 @Override public void setIdv(final IntegratedDataViewer newIdv) { 585 super.setIdv(newIdv); 586 idv = newIdv; 587 } 588 589 /** 590 * Create a window title suitable for an application window. 591 * 592 * @param title Window title 593 * 594 * @return Application title plus the window title. 595 */ 596 private String makeWindowTitle(final String title) { 597 String defaultApplicationName = "McIDAS-V"; 598 if (idv != null) { 599 defaultApplicationName = idv.getStateManager().getTitle(); 600 } 601 return UIManager.makeTitle(defaultApplicationName, title); 602 } 603 604 /** 605 * Returns the number of display components {@literal "in"} this group. 606 * 607 * @return Either the {@code size()} of the {@link List} returned by 608 * {@link #getDisplayComponents()} or {@code -1} if 609 * {@code getDisplayComponents()} returns a {@code null} {@code List}. 610 */ 611 protected int getDisplayComponentCount() { 612 @SuppressWarnings("rawtypes") 613 List dispComps = getDisplayComponents(); 614 if (dispComps == null) { 615 return -1; 616 } else { 617 return dispComps.size(); 618 } 619 } 620 621 /** 622 * Create the <tt>JPopupMenu</tt> that will be displayed for a tab. 623 * 624 * @return Menu initialized with tab options 625 */ 626 protected JPopupMenu doMakeTabMenu() { 627 ActionListener menuListener = new ActionListener() { 628 public void actionPerformed(ActionEvent evt) { 629 final String cmd = evt.getActionCommand(); 630 if (CMD_DISPLAY_EJECT.equals(cmd)) { 631 ejectDisplay(tabbedPane.getSelectedIndex()); 632 } else if (CMD_DISPLAY_RENAME.equals(cmd)) { 633 renameDisplay(tabbedPane.getSelectedIndex()); 634 } else if (CMD_DISPLAY_DESTROY.equals(cmd)) { 635 destroyDisplay(tabbedPane.getSelectedIndex()); 636 } 637 } 638 }; 639 640 final JPopupMenu popup = new JPopupMenu(); 641 JMenuItem item; 642 643 // URL img = getClass().getResource(ICO_UNDOCK); 644 // item = new JMenuItem("Undock", new ImageIcon(img)); 645 // item.setActionCommand(CMD_DISPLAY_EJECT); 646 // item.addActionListener(menuListener); 647 // popup.add(item); 648 649 URL img = getClass().getResource(ICO_RENAME); 650 item = new JMenuItem("Rename", new ImageIcon(img)); 651 item.setActionCommand(CMD_DISPLAY_RENAME); 652 item.addActionListener(menuListener); 653 popup.add(item); 654 655 // popup.addSeparator(); 656 657 img = getClass().getResource(ICO_CLOSE); 658 item = new JMenuItem("Close", new ImageIcon(img)); 659 item.setActionCommand(CMD_DISPLAY_DESTROY); 660 item.addActionListener(menuListener); 661 popup.add(item); 662 663 popup.setBorder(new BevelBorder(BevelBorder.RAISED)); 664 665 Msg.translateTree(popup); 666 return popup; 667 } 668 669 /** 670 * Remove the component holder at index {@code idx}. This method does 671 * not destroy the component holder. 672 * 673 * @param idx Index of the ejected component holder. 674 * 675 * @return Component holder that was ejected. 676 */ 677 private ComponentHolder ejectDisplay(final int idx) { 678 return null; 679 } 680 681 /** 682 * Prompt the user to change the name of the component holder at index 683 * {@code idx}. Nothing happens if the user doesn't enter anything. 684 * 685 * @param idx Index of the component holder. 686 */ 687 protected void renameDisplay(final int idx) { 688 final String title = 689 JOptionPane.showInputDialog( 690 IdvWindow.getActiveWindow().getFrame(), "Enter new name", 691 makeWindowTitle("Rename Tab"), JOptionPane.PLAIN_MESSAGE); 692 693 if (title == null) { 694 return; 695 } 696 697// final List<ComponentHolder> comps = getDisplayComponents(); 698// comps.get(idx).setName(title); 699 getHolderAt(idx).setName(title); 700 tabRenamed = true; 701 if (window != null) { 702 window.setTitle(makeWindowTitle(title)); 703 } 704 redoLayout(); 705 } 706 707 /** 708 * Prompts the user to confirm removal of the component holder at index 709 * {@code idx}. Nothing happens if the user declines. 710 * 711 * @param idx Index of the component holder. 712 * 713 * @return Either {@code true} if the user elected to remove, 714 * {@code false} otherwise. 715 */ 716 protected boolean destroyDisplay(final int idx) { 717// final List<IdvComponentHolder> comps = getDisplayComponents(); 718// IdvComponentHolder comp = comps.get(idx); 719 return ((IdvComponentHolder)getHolderAt(idx)).removeDisplayComponent(); 720// return comp.removeDisplayComponent(); 721 } 722 723 /** 724 * Remove the component at {@code index} without forcing the IDV-land 725 * component group to redraw. 726 * 727 * @param index The index of the component to be removed. 728 * 729 * @return The removed component. 730 */ 731 @SuppressWarnings("unchecked") 732 public ComponentHolder quietRemoveComponentAt(final int index) { 733 List<ComponentHolder> comps = getDisplayComponents(); 734 if (comps == null || comps.size() == 0) { 735 return null; 736 } 737 ComponentHolder removed = comps.remove(index); 738 removed.setParent(null); 739 return removed; 740 } 741 742 /** 743 * Adds a component to the end of the list of display components without 744 * forcing the IDV-land code to redraw. 745 * 746 * @param component The component to add. 747 * 748 * @return The index of the newly added component, or {@code -1} if 749 * {@link #getDisplayComponents()} returned a null {@code List}. 750 */ 751 @SuppressWarnings("unchecked") 752 public int quietAddComponent(final ComponentHolder component) { 753 List<ComponentHolder> comps = getDisplayComponents(); 754 if (comps == null) { 755 return -1; 756 } 757 if (comps.contains(component)) { 758 comps.remove(component); 759 } 760 comps.add(component); 761 component.setParent(this); 762 return comps.indexOf(component); 763 } 764 765 /** 766 * Handle pop-up events for tabs. 767 */ 768 @SuppressWarnings("unused") 769 private class TabPopupListener extends MouseAdapter { 770 771 @Override public void mouseClicked(final MouseEvent evt) { 772 checkPopup(evt); 773 } 774 775 @Override public void mousePressed(final MouseEvent evt) { 776 checkPopup(evt); 777 } 778 779 @Override public void mouseReleased(final MouseEvent evt) { 780 checkPopup(evt); 781 } 782 783 /** 784 * <p> 785 * Determines whether or not the tab popup menu should be shown, and 786 * if so, which parts of it should be enabled or disabled. 787 * </p> 788 * 789 * @param evt Allows us to determine the type of event. 790 */ 791 private void checkPopup(final MouseEvent evt) { 792 if (evt.isPopupTrigger()) { 793 // can't close or eject last tab 794 // TODO: re-evaluate this 795 Component[] comps = popup.getComponents(); 796 for (Component comp : comps) { 797 if (comp instanceof JMenuItem) { 798 String cmd = ((JMenuItem)comp).getActionCommand(); 799 if ((CMD_DISPLAY_DESTROY.equals(cmd) || CMD_DISPLAY_EJECT.equals(cmd)) 800 && tabbedPane.getTabCount() == 1) { 801 comp.setEnabled(false); 802 } else { 803 comp.setEnabled(true); 804 } 805 } 806 } 807 popup.show(tabbedPane, evt.getX(), evt.getY()); 808 } 809 } 810 } 811}