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