001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2017 005 * Space Science and Engineering Center (SSEC) 006 * University of Wisconsin - Madison 007 * 1225 W. Dayton Street, Madison, WI 53706, USA 008 * https://www.ssec.wisc.edu/mcidas 009 * 010 * All Rights Reserved 011 * 012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 013 * some McIDAS-V source code is based on IDV and VisAD source code. 014 * 015 * McIDAS-V is free software; you can redistribute it and/or modify 016 * it under the terms of the GNU Lesser Public License as published by 017 * the Free Software Foundation; either version 3 of the License, or 018 * (at your option) any later version. 019 * 020 * McIDAS-V is distributed in the hope that it will be useful, 021 * but WITHOUT ANY WARRANTY; without even the implied warranty of 022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 023 * GNU Lesser Public License for more details. 024 * 025 * You should have received a copy of the GNU Lesser Public License 026 * along with this program. If not, see http://www.gnu.org/licenses. 027 */ 028 029package edu.wisc.ssec.mcidasv.ui; 030 031import static java.util.Objects.requireNonNull; 032 033import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.arrList; 034import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.list; 035import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.newHashSet; 036import static edu.wisc.ssec.mcidasv.util.XPathUtils.elements; 037 038import java.awt.BorderLayout; 039import java.awt.Color; 040import java.awt.Component; 041import java.awt.Dimension; 042import java.awt.Font; 043import java.awt.Graphics; 044import java.awt.Insets; 045import java.awt.Rectangle; 046import java.awt.Toolkit; 047import java.awt.event.ActionEvent; 048import java.awt.event.ActionListener; 049import java.awt.event.ComponentEvent; 050import java.awt.event.ComponentListener; 051import java.awt.event.MouseAdapter; 052import java.awt.event.MouseEvent; 053import java.awt.event.MouseListener; 054import java.awt.event.WindowEvent; 055import java.awt.event.WindowListener; 056import java.lang.reflect.Method; 057import java.net.URL; 058import java.util.ArrayList; 059import java.util.Collection; 060import java.util.Collections; 061import java.util.HashMap; 062import java.util.Hashtable; 063import java.util.LinkedHashMap; 064import java.util.LinkedHashSet; 065import java.util.LinkedList; 066import java.util.List; 067import java.util.Locale; 068import java.util.Map; 069import java.util.Set; 070import java.util.Map.Entry; 071import java.util.Vector; 072import java.util.concurrent.ConcurrentHashMap; 073 074import javax.swing.AbstractAction; 075import javax.swing.Action; 076import javax.swing.BorderFactory; 077import javax.swing.ButtonGroup; 078import javax.swing.Icon; 079import javax.swing.ImageIcon; 080import javax.swing.JButton; 081import javax.swing.JCheckBox; 082import javax.swing.JComboBox; 083import javax.swing.JComponent; 084import javax.swing.JDialog; 085import javax.swing.JFrame; 086import javax.swing.JLabel; 087import javax.swing.JMenu; 088import javax.swing.JMenuBar; 089import javax.swing.JMenuItem; 090import javax.swing.JPanel; 091import javax.swing.JPopupMenu; 092import javax.swing.JRadioButtonMenuItem; 093import javax.swing.JScrollPane; 094import javax.swing.JTabbedPane; 095import javax.swing.JTextArea; 096import javax.swing.JTextField; 097import javax.swing.JToolBar; 098import javax.swing.JTree; 099import javax.swing.KeyStroke; 100import javax.swing.ScrollPaneConstants; 101import javax.swing.SwingUtilities; 102import javax.swing.WindowConstants; 103import javax.swing.border.BevelBorder; 104import javax.swing.event.MenuEvent; 105import javax.swing.event.MenuListener; 106import javax.swing.text.JTextComponent; 107import javax.swing.tree.DefaultMutableTreeNode; 108import javax.swing.tree.DefaultTreeCellRenderer; 109import javax.swing.tree.DefaultTreeModel; 110import javax.swing.tree.TreeSelectionModel; 111 112import org.slf4j.Logger; 113import org.slf4j.LoggerFactory; 114import org.w3c.dom.Document; 115import org.w3c.dom.Element; 116import org.w3c.dom.NodeList; 117 118import ucar.unidata.data.DataChoice; 119import ucar.unidata.data.DataOperand; 120import ucar.unidata.data.DataSelection; 121import ucar.unidata.data.DataSourceImpl; 122import ucar.unidata.data.DerivedDataChoice; 123import ucar.unidata.data.UserOperandValue; 124import ucar.unidata.geoloc.LatLonPointImpl; 125import ucar.unidata.idv.ControlDescriptor; 126import ucar.unidata.idv.IdvPersistenceManager; 127import ucar.unidata.idv.IdvPreferenceManager; 128import ucar.unidata.idv.IdvResourceManager; 129import ucar.unidata.idv.IntegratedDataViewer; 130import ucar.unidata.idv.SavedBundle; 131import ucar.unidata.idv.ViewManager; 132import ucar.unidata.idv.ViewState; 133import ucar.unidata.idv.IdvResourceManager.XmlIdvResource; 134import ucar.unidata.idv.control.DisplayControlImpl; 135import ucar.unidata.idv.ui.DataControlDialog; 136import ucar.unidata.idv.ui.DataSelectionWidget; 137import ucar.unidata.idv.ui.IdvComponentGroup; 138import ucar.unidata.idv.ui.IdvComponentHolder; 139import ucar.unidata.idv.ui.IdvUIManager; 140import ucar.unidata.idv.ui.IdvWindow; 141import ucar.unidata.idv.ui.IdvXmlUi; 142import ucar.unidata.idv.ui.ViewPanel; 143import ucar.unidata.idv.ui.WindowInfo; 144import ucar.unidata.metdata.NamedStationTable; 145import ucar.unidata.ui.ComponentHolder; 146import ucar.unidata.ui.HttpFormEntry; 147import ucar.unidata.ui.LatLonWidget; 148import ucar.unidata.ui.RovingProgress; 149import ucar.unidata.ui.XmlUi; 150import ucar.unidata.util.GuiUtils; 151import ucar.unidata.util.IOUtil; 152import ucar.unidata.util.LayoutUtil; 153import ucar.unidata.util.LogUtil; 154import ucar.unidata.util.MenuUtil; 155import ucar.unidata.util.Misc; 156import ucar.unidata.util.Msg; 157import ucar.unidata.util.ObjectListener; 158import ucar.unidata.util.PatternFileFilter; 159import ucar.unidata.util.StringUtil; 160import ucar.unidata.util.TwoFacedObject; 161import ucar.unidata.xml.XmlResourceCollection; 162import ucar.unidata.xml.XmlUtil; 163import edu.wisc.ssec.mcidasv.Constants; 164import edu.wisc.ssec.mcidasv.McIDASV; 165import edu.wisc.ssec.mcidasv.PersistenceManager; 166import edu.wisc.ssec.mcidasv.StateManager; 167import edu.wisc.ssec.mcidasv.supportform.McvStateCollector; 168import edu.wisc.ssec.mcidasv.supportform.SupportForm; 169import edu.wisc.ssec.mcidasv.util.Contract; 170import edu.wisc.ssec.mcidasv.util.McVGuiUtils; 171import edu.wisc.ssec.mcidasv.util.MemoryMonitor; 172 173/** 174 * <p>Derive our own UI manager to do some specific things: 175 * 176 * <ul> 177 * <li>Removing displays</li> 178 * <li>Showing the dashboard</li> 179 * <li>Adding toolbar customization options</li> 180 * <li>Implement the McIDAS-V toolbar as a JToolbar.</li> 181 * <li>Deal with bundles without component groups.</li> 182 * </ul> 183 */ 184// TODO: investigate moving similar unpersisting code to persistence manager. 185public class UIManager extends IdvUIManager implements ActionListener { 186 187 private static final Logger logger = LoggerFactory.getLogger(UIManager.class); 188 189 /** Id of the "New Display Tab" menu item for the file menu */ 190 public static final String MENU_NEWDISPLAY_TAB = "file.new.display.tab"; 191 192 /** The tag in the xml ui for creating the special example chooser */ 193 public static final String TAG_EXAMPLECHOOSER = "examplechooser"; 194 195 /** 196 * Used to keep track of ViewManagers inside a bundle. 197 */ 198 public static final HashMap<String, ViewManager> savedViewManagers = 199 new HashMap<>(); 200 201 /** 202 * Property name for whether or not the description field of the support 203 * form should perform line wrapping. 204 * */ 205 public static final String PROP_WRAP_SUPPORT_DESC = 206 "mcidasv.supportform.wrap"; 207 208 /** Action command for manipulating the size of the toolbar icons. */ 209 private static final String ACT_ICON_TYPE = "action.toolbar.seticonsize"; 210 211 /** Action command for removing all displays */ 212 private static final String ACT_REMOVE_DISPLAYS = "action.displays.remove"; 213 214 /** Action command for showing the dashboard */ 215 private static final String ACT_SHOW_DASHBOARD = "action.dashboard.show"; 216 217 /** Action command for showing the dashboard */ 218 private static final String ACT_SHOW_DATASELECTOR = "action.dataselector.show"; 219 220 /** Action command for showing the dashboard */ 221 private static final String ACT_SHOW_DISPLAYCONTROLLER = "action.displaycontroller.show"; 222 223 /** Action command for displaying the toolbar preference tab. */ 224 private static final String ACT_SHOW_PREF = "action.toolbar.showprefs"; 225 226 /** Message shown when an unknown action is in the toolbar. */ 227 private static final String BAD_ACTION_MSG = "Unknown action (%s) found in your toolbar. McIDAS-V will continue to load, but there will be no button associated with %s."; 228 229 /** Menu ID for the {@literal "Restore Saved Views"} submenu. */ 230 public static final String MENU_NEWVIEWS = "menu.tools.projections.restoresavedviews"; 231 232 /** Label for the "link" to the toolbar customization preference tab. */ 233 private static final String LBL_TB_EDITOR = "Customize..."; 234 235 /** Current representation of toolbar actions. */ 236 private ToolbarStyle currentToolbarStyle = 237 getToolbarStylePref(ToolbarStyle.MEDIUM); 238 239 /** The IDV property that reflects the size of the icons. */ 240 private static final String PROP_ICON_SIZE = "mcv.ui.iconsize"; 241 242 /** The URL of the script that processes McIDAS-V support requests. */ 243 private static final String SUPPORT_REQ_URL = 244 "https://www.ssec.wisc.edu/mcidas/misc/mc-v/supportreq/support.php"; 245 246 /** Separator to use between window title components. */ 247 protected static final String TITLE_SEPARATOR = " - "; 248 249 /** 250 * The currently "displayed" actions. Keeping this List allows us to get 251 * away with only reading the XML files upon starting the application and 252 * only writing the XML files upon exiting the application. This will avoid 253 * those redrawing delays. 254 */ 255 private List<String> cachedButtons; 256 257 /** Stores all available actions. */ 258 private final IdvActions idvActions; 259 260 /** Map of skin ids to their skin resource index. */ 261 private Map<String, Integer> skinIds = readSkinIds(); 262 263 /** An easy way to figure out who is holding a given ViewManager. */ 264 private Map<ViewManager, ComponentHolder> viewManagers = new HashMap<>(); 265 266 private int componentHolderCount; 267 268 private int componentGroupCount; 269 270 /** Cache for the results of {@link #getWindowTitleFromSkin(int)}. */ 271 private final Map<Integer, String> skinToTitle = new ConcurrentHashMap<>(); 272 273 /** Maps menu IDs to {@link JMenu}s. */ 274// private Hashtable<String, JMenu> menuIds; 275 private Hashtable<String, JMenuItem> menuIds; 276 277 /** The splash screen (minus easter egg). */ 278 private McvSplash splash; 279 280 /** 281 * A list of the toolbars that the IDV is playing with. Used to apply 282 * changes to *all* the toolbars in the application. 283 */ 284 private List<JToolBar> toolbars; 285 286 /** 287 * Keeping the reference to the toolbar menu mouse listener allows us to 288 * avoid constantly rebuilding the menu. 289 */ 290 private MouseListener toolbarMenu; 291 292 /** Keep the dashboard around so we don't have to re-create it each time. */ 293 protected IdvWindow dashboard; 294 295 /** False until {@link #initDone()}. */ 296 protected boolean initDone = false; 297 298 /** IDV instantiation--nice to keep around to reduce getIdv() calls. */ 299 private IntegratedDataViewer idv; 300 301 /** 302 * Hands off our IDV instantiation to IdvUiManager. 303 * 304 * @param idv The idv 305 */ 306 public UIManager(IntegratedDataViewer idv) { 307 super(idv); 308 309 this.idv = idv; 310 311 // cache the appropriate data for the toolbar. it'll make updates 312 // much snappier 313 idvActions = new IdvActions(getIdv(), IdvResourceManager.RSC_ACTIONS); 314 cachedButtons = readToolbar(); 315 } 316 317 /** 318 * Override the IDV method so that we hide component group button. 319 */ 320 @Override public IdvWindow createNewWindow(List viewManagers, 321 boolean notifyCollab, String title, String skinPath, Element skinRoot, 322 boolean show, WindowInfo windowInfo) 323 { 324 325 if (windowInfo != null) { 326 logger.trace("creating window: title='{}' bounds: {}", title, windowInfo.getBounds()); 327 } else { 328 logger.trace("creating window: title='{}' bounds: (no windowinfo)", title); 329 } 330 331 if (Constants.DATASELECTOR_NAME.equals(title)) { 332 show = false; 333 } 334 if (skinPath.contains("dashboard.xml")) { 335 show = false; 336 } 337 338 // used to force any new "display" windows to be the same size as the current window. 339 IdvWindow previousWindow = IdvWindow.getActiveWindow(); 340 341 IdvWindow w = super.createNewWindow(viewManagers, notifyCollab, title, 342 skinPath, skinRoot, show, windowInfo); 343 344 String iconPath = idv.getProperty(Constants.PROP_APP_ICON, null); 345 ImageIcon icon = GuiUtils.getImageIcon(iconPath, getClass(), true); 346 w.setIconImage(icon.getImage()); 347 348 // try to catch the dashboard 349 if (Constants.DATASELECTOR_NAME.equals(w.getTitle())) { 350 setDashboard(w); 351 } else if (!w.getComponentGroups().isEmpty()) { 352 // otherwise we need to hide the component group header and explicitly 353 // set the size of the window. 354 w.getComponentGroups().get(0).setShowHeader(false); 355 if (previousWindow != null) { 356 Rectangle r = previousWindow.getBounds(); 357 358 w.setBounds(new Rectangle(r.x, r.y, r.width, r.height)); 359 } 360 } else { 361 logger.trace("creating window with no component groups"); 362 } 363 364 initDisplayShortcuts(w); 365 366 RovingProgress progress = 367 (RovingProgress)w.getComponent(IdvUIManager.COMP_PROGRESSBAR); 368 369 if (progress != null) { 370 progress.start(); 371 } 372 return w; 373 } 374 375 /** 376 * Create the display window described by McIDAS-V's default display skin 377 * 378 * @return {@link IdvWindow} that was created. 379 */ 380 public IdvWindow buildDefaultSkin() { 381 return createNewWindow(new ArrayList(), false); 382 } 383 384 /** 385 * Create a new IdvWindow for the given viewManager. Put the 386 * contents of the viewManager into the window 387 * 388 * @return The new window 389 */ 390 public IdvWindow buildEmptyWindow() { 391 Element root = null; 392 String path = null; 393 String skinName = null; 394 395 path = getIdv().getProperty("mcv.ui.emptycompgroup", (String)null); 396 if (path != null) { 397 path = path.trim(); 398 } 399 400 if ((path != null) && (path.length() > 0)) { 401 try { 402 root = XmlUtil.getRoot(path, getClass()); 403 skinName = getStateManager().getTitle(); 404 String tmp = XmlUtil.getAttribute(root, "name", (String)null); 405 if (tmp == null) { 406 tmp = IOUtil.stripExtension(IOUtil.getFileTail(path)); 407 } 408 skinName = skinName + " - " + tmp; 409 } catch (Exception exc) { 410 logger.error("error building empty window", exc); 411 } 412 } 413 414 IdvWindow window = createNewWindow(new ArrayList(), false, skinName, path, root, true, null); 415 window.setVisible(true); 416 return window; 417 } 418 419 420 /** 421 * Sets {@link #dashboard} to {@code window}. This method also adds some 422 * listeners to {@code window} so that the state of the dashboard is 423 * automatically saved. 424 * 425 * @param window The dashboard. Nothing happens if {@link #dashboard} has 426 * already been set, or this parameter is {@code null}. 427 */ 428 private void setDashboard(final IdvWindow window) { 429 if (window == null || dashboard != null) 430 return; 431 432 dashboard = window; 433 dashboard.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); 434 435 final Component comp = dashboard.getComponent(); 436 final ucar.unidata.idv.StateManager state = getIdv().getStateManager(); 437 438 // for some reason the component listener's "componentHidden" method 439 // would not fire the *first* time the dashboard is closed/hidden. 440 // the window listener catches it. 441 dashboard.addWindowListener(new WindowListener() { 442 public void windowClosed(final WindowEvent e) { 443 Boolean saveViz = (Boolean)state.getPreference(Constants.PREF_SAVE_DASHBOARD_VIZ, Boolean.FALSE); 444 if (saveViz) { 445 state.putPreference(Constants.PROP_SHOWDASHBOARD, false); 446 } 447 } 448 449 public void windowActivated(final WindowEvent e) { } 450 public void windowClosing(final WindowEvent e) { } 451 public void windowDeactivated(final WindowEvent e) { } 452 public void windowDeiconified(final WindowEvent e) { } 453 public void windowIconified(final WindowEvent e) { } 454 public void windowOpened(final WindowEvent e) { } 455 }); 456 457 dashboard.getComponent().addComponentListener(new ComponentListener() { 458 public void componentMoved(final ComponentEvent e) { 459 state.putPreference(Constants.PROP_DASHBOARD_BOUNDS, comp.getBounds()); 460 } 461 462 public void componentResized(final ComponentEvent e) { 463 state.putPreference(Constants.PROP_DASHBOARD_BOUNDS, comp.getBounds()); 464 } 465 466 public void componentShown(final ComponentEvent e) { 467 Boolean saveViz = (Boolean)state.getPreference(Constants.PREF_SAVE_DASHBOARD_VIZ, Boolean.FALSE); 468 if (saveViz) 469 state.putPreference(Constants.PROP_SHOWDASHBOARD, true); 470 } 471 472 public void componentHidden(final ComponentEvent e) { 473 Boolean saveViz = (Boolean)state.getPreference(Constants.PREF_SAVE_DASHBOARD_VIZ, Boolean.FALSE); 474 if (saveViz) 475 state.putPreference(Constants.PROP_SHOWDASHBOARD, false); 476 } 477 }); 478 479 Rectangle bounds = (Rectangle)state.getPreferenceOrProperty(Constants.PROP_DASHBOARD_BOUNDS); 480 if (bounds != null) 481 comp.setBounds(bounds); 482 } 483 484 /** 485 * Attempts to add all component holders in {@code info} to 486 * {@code group}. Especially useful when unpersisting a bundle and 487 * attempting to deal with its component groups. 488 * 489 * @param info The window we want to process. 490 * @param group Receives the holders in {@code info}. 491 * 492 * @return True if there were component groups in {@code info}. 493 */ 494 public boolean unpersistComponentGroups(final WindowInfo info, 495 final McvComponentGroup group) { 496 Collection<Object> comps = info.getPersistentComponents().values(); 497 498 if (comps.isEmpty()) { 499 return false; 500 } 501 502 for (Object comp : comps) { 503 // comp is typically always an IdvComponentGroup, but there are 504 // no guarantees... 505 if (! (comp instanceof IdvComponentGroup)) { 506 System.err.println("DEBUG: non IdvComponentGroup found in persistent components: " 507 + comp.getClass().getName()); 508 continue; 509 } 510 511 IdvComponentGroup bundleGroup = (IdvComponentGroup)comp; 512 513 // need to make a copy of this list to avoid a rogue 514 // ConcurrentModificationException 515 // TODO: determine which threads are clobbering each other. 516 List<IdvComponentHolder> holders = 517 new ArrayList<>(bundleGroup.getDisplayComponents()); 518 519 for (IdvComponentHolder holder : holders) { 520 group.quietAddComponent(holder); 521 } 522 523 group.redoLayout(); 524 525 JComponent groupContents = group.getContents(); 526 groupContents.setPreferredSize(groupContents.getSize()); 527 } 528 return true; 529 } 530 531 /** 532 * Override IdvUIManager's loadLookAndFeel so that we can force the IDV to 533 * load the Aqua look and feel if requested from the command line. 534 */ 535 @Override public void loadLookAndFeel() { 536 if (McIDASV.useAquaLookAndFeel) { 537 // since we must rely on the IDV to do the actual loading (due to 538 // our UIManager's name conflicting with javax.swing.UIManager's 539 // name), save the user's preference, replace it temporarily and 540 // have the IDV do its thing, then overwrite the temp preference 541 // with the saved preference. Blah! 542 String previousLF = getStore().get(PREF_LOOKANDFEEL, (String)null); 543 getStore().put(PREF_LOOKANDFEEL, "apple.laf.AquaLookAndFeel"); 544 super.loadLookAndFeel(); 545 getStore().put(PREF_LOOKANDFEEL, previousLF); 546 547 // locale code was taken out of IDVUIManager's "loadLookAndFeel". 548 // 549 // if the locale preference is set to "system", there will *NOT* 550 // be a PREF_LOCALE -> "Locale.EXAMPLE" key/value pair in main.xml; 551 // as you're using the default state of the preference. 552 // this also means that if there is a non-null value associated 553 // with PREF_LOCALE, the user has selected the US locale. 554 String locale = getStore().get(PREF_LOCALE, (String)null); 555 if (locale != null) { 556 Locale.setDefault(Locale.US); 557 } 558 } else { 559 super.loadLookAndFeel(); 560 } 561 } 562 563 @Override public void handleWindowActivated(final IdvWindow window) { 564 List<ViewManager> viewManagers = window.getViewManagers(); 565 ViewManager newActive = null; 566 long lastActivatedTime = -1; 567 568 for (ViewManager viewManager : viewManagers) { 569 if (viewManager.getContents() == null) 570 continue; 571 572 if (!viewManager.getContents().isVisible()) 573 continue; 574 575 lastActiveFrame = window; 576 577 if (viewManager.getLastTimeActivated() > lastActivatedTime) { 578 newActive = viewManager; 579 lastActivatedTime = viewManager.getLastTimeActivated(); 580 } 581 } 582 583 if (newActive != null) { 584 getVMManager().setLastActiveViewManager(newActive); 585 } 586 } 587 588 /** 589 * Handles the windowing portions of bundle loading: wraps things in 590 * component groups (if needed), merges things into existing windows or 591 * creates new windows, and removes displays and data if asked nicely. 592 * 593 * @param windows WindowInfos from the bundle. 594 * @param newViewManagers ViewManagers stored in the bundle. 595 * @param okToMerge Put bundled things into an existing window? 596 * @param fromCollab Did this come from the collab stuff? 597 * @param didRemoveAll Remove all data and displays? 598 * 599 * @see IdvUIManager#unpersistWindowInfo(List, List, boolean, boolean, 600 * boolean) 601 */ 602 @Override public void unpersistWindowInfo(List windows, 603 List newViewManagers, boolean okToMerge, boolean fromCollab, 604 boolean didRemoveAll) 605 { 606 if (newViewManagers == null) { 607 newViewManagers = new ArrayList<>(); 608 } 609 610 // keep track of the "old" state if the user wants to remove things. 611 boolean mergeLayers = ((PersistenceManager)getPersistenceManager()).getMergeBundledLayers(); 612 List<IdvComponentHolder> holdersBefore = new ArrayList<>(); 613 List<IdvWindow> windowsBefore = new ArrayList<>(); 614 if (didRemoveAll) { 615 holdersBefore.addAll(McVGuiUtils.getAllComponentHolders()); 616 windowsBefore.addAll(McVGuiUtils.getAllDisplayWindows()); 617 } 618 619 for (WindowInfo info : (List<WindowInfo>)windows) { 620 newViewManagers.removeAll(info.getViewManagers()); 621 makeBundledDisplays(info, okToMerge, mergeLayers, fromCollab); 622 623 if (mergeLayers) { 624 holdersBefore.addAll(McVGuiUtils.getComponentHolders(info)); 625 } 626 } 627// System.err.println("holdersBefore="+holdersBefore); 628 // no reason to kill the displays if there aren't any windows in the 629 // bundle! 630 if ((mergeLayers) || (didRemoveAll && !windows.isEmpty())) { 631 killOldDisplays(holdersBefore, windowsBefore, (okToMerge || mergeLayers)); 632 } 633 } 634 635 /** 636 * Removes data and displays that existed prior to loading a bundle. 637 * 638 * @param oldHolders Component holders around before loading. 639 * @param oldWindows Windows around before loading. 640 * @param merge Were the bundle contents merged into an existing window? 641 */ 642 public void killOldDisplays(final List<IdvComponentHolder> oldHolders, 643 final List<IdvWindow> oldWindows, final boolean merge) 644 { 645// System.err.println("killOldDisplays: merge="+merge); 646 // if we merged, this will ensure that any old holders in the merged 647 // window also get removed. 648 if (merge) { 649 for (IdvComponentHolder holder : oldHolders) { 650 holder.doRemove(); 651 } 652 } 653 654 // mop up any windows that no longer have component holders. 655 for (IdvWindow window : oldWindows) { 656 IdvComponentGroup group = McVGuiUtils.getComponentGroup(window); 657 658 List<IdvComponentHolder> holders = 659 McVGuiUtils.getComponentHolders(group); 660 661 // if the old set of holders contains all of this window's 662 // holders, this window can be deleted: 663 // 664 // this works fine for merging because the okToMerge stuff will 665 // remove all old holders from the current window, but if the 666 // bundle was merged into this window, containsAll() will fail 667 // due to there being a new holder. 668 // 669 // if the bundle was loaded into its own window, then 670 // all the old windows will pass this test. 671 if (oldHolders.containsAll(holders)) { 672 group.doRemove(); 673 window.dispose(); 674 } 675 } 676 } 677 678 679 /** 680 * A hack because Unidata moved the skins (taken from 681 * {@link IdvPersistenceManager}). 682 * 683 * @param skinPath original path 684 * @return fixed path 685 */ 686 private String fixSkinPath(String skinPath) { 687 if (skinPath == null) { 688 return null; 689 } 690 if (StringUtil.stringMatch( 691 skinPath, "^/ucar/unidata/idv/resources/[^/]+\\.xml")) { 692 skinPath = 693 StringUtil.replace(skinPath, "/ucar/unidata/idv/resources/", 694 "/ucar/unidata/idv/resources/skins/"); 695 } 696 return skinPath; 697 } 698 699 /** 700 * <p> 701 * Uses the contents of {@code info} to rebuild a display that has been 702 * bundled. If {@code merge} is true, the displayable parts of the bundle 703 * will be put into the current window. Otherwise a new window is created 704 * and the relevant parts of the bundle will occupy that new window. 705 * </p> 706 * 707 * @param info WindowInfo to use with creating the new window. 708 * @param merge Merge created things into an existing window? 709 * @param mergeLayers Whether or not layers should be merged. 710 * @param fromCollab Whether or not this is in response to a 711 * {@literal "collaboration"} event. 712 */ 713 public void makeBundledDisplays(final WindowInfo info, final boolean merge, final boolean mergeLayers, final boolean fromCollab) { 714 // need a way to get the last active view manager (for real) 715 IdvWindow window = IdvWindow.getActiveWindow(); 716 ViewManager last = ((PersistenceManager)getPersistenceManager()).getLastViewManager(); 717 String skinPath = info.getSkinPath(); 718 719 // create a new window if we're not merging (or the active window is 720 // invalid), otherwise sticking with the active window is fine. 721 if ((merge || (mergeLayers)) && last != null) { 722 List<IdvWindow> windows = IdvWindow.getWindows(); 723 for (IdvWindow tmpWindow : windows) { 724 if (tmpWindow.getComponentGroups().isEmpty()) { 725 continue; 726 } 727 List<IdvComponentGroup> groups = tmpWindow.getComponentGroups(); 728 for (IdvComponentGroup group : groups) { 729 List<IdvComponentHolder> holders = group.getDisplayComponents(); 730 for (IdvComponentHolder holder : holders) { 731 List<ViewManager> vms = holder.getViewManagers(); 732 if (vms != null && vms.contains(last)) { 733 window = tmpWindow; 734 735 if (mergeLayers) { 736 mergeLayers(info, window, fromCollab); 737 } 738 break; 739 } 740 } 741 } 742 } 743 } 744 else if ((window == null) || (!merge) || (window.getComponentGroups().isEmpty())) { 745 try { 746 Element skinRoot = 747 XmlUtil.getRoot(Constants.BLANK_COMP_GROUP, getClass()); 748 749 window = createNewWindow(null, false, "McIDAS-V", 750 Constants.BLANK_COMP_GROUP, skinRoot, false, null); 751 752 window.setBounds(info.getBounds()); 753 window.setVisible(true); 754 755 } catch (Throwable e) { 756 e.printStackTrace(); 757 } 758 } 759 760 McvComponentGroup group = 761 (McvComponentGroup)window.getComponentGroups().get(0); 762 763 // if the bundle contains only component groups, ensure they get merged 764 // into group. 765 unpersistComponentGroups(info, group); 766 } 767 768 private void mergeLayers(final WindowInfo info, final IdvWindow window, final boolean fromCollab) { 769 List<ViewManager> newVms = McVGuiUtils.getViewManagers(info); 770 List<ViewManager> oldVms = McVGuiUtils.getViewManagers(window); 771 772 if (oldVms.size() == newVms.size()) { 773 List<ViewManager> merged = new ArrayList<>(); 774 for (int vmIdx = 0; 775 (vmIdx < newVms.size()) 776 && (vmIdx < oldVms.size()); 777 vmIdx++) 778 { 779 ViewManager newVm = newVms.get(vmIdx); 780 ViewManager oldVm = oldVms.get(vmIdx); 781 if (oldVm.canBe(newVm)) { 782 oldVm.initWith(newVm, fromCollab); 783 merged.add(newVm); 784 } 785 } 786 787 Collection<Object> comps = info.getPersistentComponents().values(); 788 789 for (Object comp : comps) { 790 if (!(comp instanceof IdvComponentGroup)) 791 continue; 792 793 IdvComponentGroup group = (IdvComponentGroup)comp; 794 List<IdvComponentHolder> holders = group.getDisplayComponents(); 795 List<IdvComponentHolder> emptyHolders = new ArrayList<>(); 796 for (IdvComponentHolder holder : holders) { 797 List<ViewManager> vms = holder.getViewManagers(); 798 for (ViewManager vm : merged) { 799 if (vms.contains(vm)) { 800 vms.remove(vm); 801 getVMManager().removeViewManager(vm); 802 List<DisplayControlImpl> controls = vm.getControlsForLegend(); 803 for (DisplayControlImpl dc : controls) { 804 try { 805 dc.doRemove(); 806 } catch (Exception e) { } 807 getViewPanel().removeDisplayControl(dc); 808 getViewPanel().viewManagerDestroyed(vm); 809 810 vm.clearDisplays(); 811 812 } 813 } 814 } 815 holder.setViewManagers(vms); 816 817 if (vms.isEmpty()) { 818 emptyHolders.add(holder); 819 } 820 } 821 822 for (IdvComponentHolder holder : emptyHolders) { 823 holder.doRemove(); 824 group.removeComponent(holder); 825 } 826 } 827 } 828 } 829 830 /** 831 * Make a window title. The format for window titles is: 832 * {@literal <window>TITLE_SEPARATOR<document>} 833 * 834 * @param win Window title. 835 * @param doc Document or window sub-content. 836 * @return Formatted window title. 837 */ 838 protected static String makeTitle(final String win, final String doc) { 839 if (win == null) 840 return ""; 841 else if (doc == null) 842 return win; 843 else if (doc.equals("untitled")) 844 return win; 845 846 return win.concat(TITLE_SEPARATOR).concat(doc); 847 } 848 849 /** 850 * Make a window title. The format for window titles is: 851 * 852 * <pre> 853 * <window>TITLE_SEPARATOR<document>TITLE_SEPARATOR<other> 854 * </pre> 855 * 856 * @param window Window title. 857 * @param document Document or window sub content. 858 * @param other Other content to include. 859 * @return Formatted window title. 860 */ 861 protected static String makeTitle(final String window, 862 final String document, final String other) 863 { 864 if (other == null) 865 return makeTitle(window, document); 866 867 return window.concat(TITLE_SEPARATOR).concat(document).concat( 868 TITLE_SEPARATOR).concat(other); 869 } 870 871 /** 872 * Split window title using {@code TITLE_SEPARATOR}. 873 * 874 * @param title The window title to split 875 * @return Parts of the title with the white space trimmed. 876 */ 877 protected static String[] splitTitle(final String title) { 878 String[] splt = title.split(TITLE_SEPARATOR); 879 for (int i = 0; i < splt.length; i++) { 880 splt[i] = splt[i].trim(); 881 } 882 return splt; 883 } 884 885 /** 886 * Overridden to prevent the IDV's {@code StateManager} instantiation of {@link ucar.unidata.idv.mac.MacBridge}. 887 * McIDAS-V uses different approaches for OS X compatibility. 888 * 889 * @return Always returns {@code false}. 890 * 891 * @deprecated Use {@link edu.wisc.ssec.mcidasv.McIDASV#isMac()} instead. 892 */ 893 // TODO: be sure to bring back the override annotation once we've upgraded our idv.jar. 894 public boolean isMac() { 895 return false; 896 } 897 898 /* (non-Javadoc) 899 * @see ucar.unidata.idv.ui.IdvUIManager#about() 900 */ 901 public void about() { 902 java.awt.EventQueue.invokeLater(() -> { 903 AboutFrame frame = new AboutFrame((McIDASV)idv); 904 // pop up the window right away; the system information tab won't 905 // be populated until the user has selected the tab. 906 frame.setVisible(true); 907 }); 908 } 909 910 /** 911 * Handles all the ActionEvents that occur for widgets contained within 912 * this class. It's not so pretty, but it isolates the event handling in 913 * one place (and reduces the number of action listeners to one). 914 * 915 * @param e The event that triggered the call to this method. 916 */ 917 public void actionPerformed(ActionEvent e) { 918 String cmd = e.getActionCommand(); 919 boolean toolbarEditEvent = false; 920 921 // handle selecting large icons 922 if (cmd.startsWith(ToolbarStyle.LARGE.getAction())) { 923 currentToolbarStyle = ToolbarStyle.LARGE; 924 toolbarEditEvent = true; 925 } 926 927 // handle selecting medium icons 928 else if (cmd.startsWith(ToolbarStyle.MEDIUM.getAction())) { 929 currentToolbarStyle = ToolbarStyle.MEDIUM; 930 toolbarEditEvent = true; 931 } 932 933 // handle selecting small icons 934 else if (cmd.startsWith(ToolbarStyle.SMALL.getAction())) { 935 currentToolbarStyle = ToolbarStyle.SMALL; 936 toolbarEditEvent = true; 937 } 938 939 // handle the user selecting the show toolbar preference menu item 940 else if (cmd.startsWith(ACT_SHOW_PREF)) { 941 IdvPreferenceManager prefs = idv.getPreferenceManager(); 942 prefs.showTab(Constants.PREF_LIST_TOOLBAR); 943 toolbarEditEvent = true; 944 } 945 946 // handle the user toggling the size of the icon 947 else if (cmd.startsWith(ACT_ICON_TYPE)) 948 toolbarEditEvent = true; 949 950 // handle the user removing displays 951 else if (cmd.startsWith(ACT_REMOVE_DISPLAYS)) 952 idv.removeAllDisplays(); 953 954 // handle popping up the dashboard. 955 else if (cmd.startsWith(ACT_SHOW_DASHBOARD)) 956 showDashboard(); 957 958 // handle popping up the data explorer. 959 else if (cmd.startsWith(ACT_SHOW_DATASELECTOR)) 960 showDashboard("Data Sources"); 961 962 // handle popping up the display controller. 963 else if (cmd.startsWith(ACT_SHOW_DISPLAYCONTROLLER)) 964 showDashboard("Layer Controls"); 965 966 else 967 System.err.println("Unsupported action event!"); 968 969 // if the user did something to change the toolbar, hide the current 970 // toolbar, replace it, and then make the new toolbar visible. 971 if (toolbarEditEvent == true) { 972 973 getStateManager().writePreference(PROP_ICON_SIZE, 974 currentToolbarStyle.getSizeAsString()); 975 976 // destroy the menu so it can be properly updated during rebuild 977 toolbarMenu = null; 978 979 for (JToolBar toolbar : toolbars) { 980 toolbar.setVisible(false); 981 populateToolbar(toolbar); 982 toolbar.setVisible(true); 983 } 984 } 985 } 986 987 public JComponent getDisplaySelectorComponent() { 988 DefaultMutableTreeNode root = new DefaultMutableTreeNode(""); 989 DefaultTreeModel model = new DefaultTreeModel(root); 990 final JTree tree = new JTree(model); 991 tree.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); 992 tree.getSelectionModel().setSelectionMode( 993 TreeSelectionModel.SINGLE_TREE_SELECTION 994 ); 995 DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer(); 996 renderer.setIcon(null); 997 renderer.setOpenIcon(null); 998 renderer.setClosedIcon(null); 999 tree.setCellRenderer(renderer); 1000 1001 for (IdvWindow w : McVGuiUtils.getAllDisplayWindows()) { 1002 String title = w.getTitle(); 1003 TwoFacedObject winTFO = new TwoFacedObject(title, w); 1004 DefaultMutableTreeNode winNode = new DefaultMutableTreeNode(winTFO); 1005 for (IdvComponentHolder h : McVGuiUtils.getComponentHolders(w)) { 1006 String hName = h.getName(); 1007 TwoFacedObject tmp = new TwoFacedObject(hName, h); 1008 DefaultMutableTreeNode holderNode = new DefaultMutableTreeNode(tmp); 1009 //for (ViewManager v : (List<ViewManager>)h.getViewManagers()) { 1010 for (int i = 0; i < h.getViewManagers().size(); i++) { 1011 ViewManager v = (ViewManager)h.getViewManagers().get(i); 1012 String vName = v.getName(); 1013 TwoFacedObject tfo = null; 1014 1015 if (vName != null && vName.length() > 0) 1016 tfo = new TwoFacedObject(vName, v); 1017 else 1018 tfo = new TwoFacedObject(Constants.PANEL_NAME + " " + (i+1), v); 1019 1020 holderNode.add(new DefaultMutableTreeNode(tfo)); 1021 } 1022 winNode.add(holderNode); 1023 } 1024 root.add(winNode); 1025 } 1026 1027 // select the appropriate view 1028 tree.addTreeSelectionListener(evt -> { 1029 DefaultMutableTreeNode node = 1030 (DefaultMutableTreeNode)tree.getLastSelectedPathComponent(); 1031 if (node == null || !(node.getUserObject() instanceof TwoFacedObject)) { 1032 return; 1033 } 1034 TwoFacedObject tfo = (TwoFacedObject) node.getUserObject(); 1035 1036 Object obj = tfo.getId(); 1037 if (obj instanceof ViewManager) { 1038 ViewManager viewManager = (ViewManager) tfo.getId(); 1039 idv.getVMManager().setLastActiveViewManager(viewManager); 1040 } else if (obj instanceof McvComponentHolder) { 1041 McvComponentHolder holder = (McvComponentHolder)obj; 1042 holder.setAsActiveTab(); 1043 } else if (obj instanceof IdvWindow) { 1044 IdvWindow window1 = (IdvWindow)obj; 1045 window1.toFront(); 1046 } 1047 }); 1048 1049 // expand all the nodes 1050 for (int i = 0; i < tree.getRowCount(); i++) { 1051 tree.expandPath(tree.getPathForRow(i)); 1052 } 1053 1054 return tree; 1055 } 1056 1057 /** 1058 * Builds the JPopupMenu that appears when a user right-clicks in the 1059 * toolbar. 1060 * 1061 * @return MouseListener that listens for right-clicks in the toolbar. 1062 */ 1063 private MouseListener constructToolbarMenu() { 1064 JMenuItem large = ToolbarStyle.LARGE.buildMenuItem(this); 1065 JMenuItem medium = ToolbarStyle.MEDIUM.buildMenuItem(this); 1066 JMenuItem small = ToolbarStyle.SMALL.buildMenuItem(this); 1067 1068 JMenuItem toolbarPrefs = new JMenuItem(LBL_TB_EDITOR); 1069 toolbarPrefs.setActionCommand(ACT_SHOW_PREF); 1070 toolbarPrefs.addActionListener(this); 1071 1072 switch (currentToolbarStyle) { 1073 case LARGE: 1074 large.setSelected(true); 1075 break; 1076 1077 case MEDIUM: 1078 medium.setSelected(true); 1079 break; 1080 1081 case SMALL: 1082 small.setSelected(true); 1083 break; 1084 1085 default: 1086 break; 1087 } 1088 1089 ButtonGroup group = new ButtonGroup(); 1090 group.add(large); 1091 group.add(medium); 1092 group.add(small); 1093 1094 JPopupMenu popup = new JPopupMenu(); 1095 popup.setBorder(new BevelBorder(BevelBorder.RAISED)); 1096 popup.add(large); 1097 popup.add(medium); 1098 popup.add(small); 1099 popup.addSeparator(); 1100 popup.add(toolbarPrefs); 1101 1102 return new PopupListener(popup); 1103 } 1104 1105 /** 1106 * Queries the stored preferences to determine the preferred 1107 * {@link ToolbarStyle}. If there was no preference, {@code defaultStyle} 1108 * is used. 1109 * 1110 * @param defaultStyle {@code ToolbarStyle} to use if there was no value 1111 * associated with the toolbar style preference. 1112 * 1113 * @return The preferred {@code ToolbarStyle} or {@code defaultStyle}. 1114 * 1115 * @throws AssertionError if {@code PROP_ICON_SIZE} had returned an integer 1116 * value that did not correspond to a valid {@code ToolbarStyle}. 1117 */ 1118 private ToolbarStyle getToolbarStylePref(final ToolbarStyle defaultStyle) { 1119 assert defaultStyle != null; 1120 String storedStyle = getStateManager().getPreferenceOrProperty(PROP_ICON_SIZE, (String)null); 1121 if (storedStyle == null) { 1122 return defaultStyle; 1123 } 1124 1125 int intSize = Integer.valueOf(storedStyle); 1126 1127 // can't switch on intSize using ToolbarStyles as the case... 1128 if (intSize == ToolbarStyle.LARGE.getSize()) { 1129 return ToolbarStyle.LARGE; 1130 } 1131 if (intSize == ToolbarStyle.MEDIUM.getSize()) { 1132 return ToolbarStyle.MEDIUM; 1133 } 1134 if (intSize == ToolbarStyle.SMALL.getSize()) { 1135 return ToolbarStyle.SMALL; 1136 } 1137 1138 // uh oh 1139 throw new AssertionError("Invalid preferred icon size: " + intSize); 1140 } 1141 1142 /** 1143 * Given a valid action and icon size, build a JButton for the toolbar. 1144 * 1145 * @param action The action whose corresponding icon we want. 1146 * 1147 * @return A JButton for the given action with an appropriate-sized icon. 1148 */ 1149 private JButton buildToolbarButton(String action) { 1150 IdvAction a = idvActions.getAction(action); 1151 if (a == null) { 1152 return null; 1153 } 1154 1155 JButton button = new JButton(idvActions.getStyledIconFor(action, currentToolbarStyle)); 1156 1157 // the IDV will take care of action handling! so nice! 1158 button.addActionListener(idv); 1159 button.setActionCommand(a.getAttribute(ActionAttribute.ACTION)); 1160 button.addMouseListener(toolbarMenu); 1161 button.setToolTipText(a.getAttribute(ActionAttribute.DESCRIPTION)); 1162 1163 return button; 1164 } 1165 1166 @Override public JPanel doMakeStatusBar(final IdvWindow window) { 1167 if (window == null) { 1168 return new JPanel(); 1169 } 1170 JLabel msgLabel = new JLabel(" "); 1171 LogUtil.addMessageLogger(msgLabel); 1172 1173 window.setComponent(COMP_MESSAGELABEL, msgLabel); 1174 1175 IdvXmlUi xmlUI = window.getXmlUI(); 1176 if (xmlUI != null) { 1177 xmlUI.addComponent(COMP_MESSAGELABEL, msgLabel); 1178 } 1179 JLabel waitLabel = new JLabel(IdvWindow.getNormalIcon()); 1180 waitLabel.addMouseListener(new ObjectListener(null) { 1181 public void mouseClicked(final MouseEvent e) { 1182 getIdv().clearWaitCursor(); 1183 } 1184 }); 1185 window.setComponent(COMP_WAITLABEL, waitLabel); 1186 1187 RovingProgress progress = doMakeRovingProgressBar(); 1188 window.setComponent(COMP_PROGRESSBAR, progress); 1189 1190// Monitoring label = new MemoryPanel(); 1191// ((McIDASV)getIdv()).getMonitorManager().addListener(label); 1192// window.setComponent(Constants.COMP_MONITORPANEL, label); 1193 1194 boolean isClockShowing = Boolean.getBoolean(getStateManager().getPreferenceOrProperty(PROP_SHOWCLOCK_VIEW, "true")); 1195 MemoryMonitor mm = new MemoryMonitor(getStateManager(), 75, 95, isClockShowing); 1196 mm.setBorder(getStatusBorder()); 1197 1198 // MAKE PRETTY NOW! 1199 progress.setBorder(getStatusBorder()); 1200 waitLabel.setBorder(getStatusBorder()); 1201 msgLabel.setBorder(getStatusBorder()); 1202// ((JPanel)label).setBorder(getStatusBorder()); 1203 1204// JPanel msgBar = GuiUtils.leftCenter((JPanel)label, msgLabel); 1205 JPanel msgBar = LayoutUtil.leftCenter(mm, msgLabel); 1206 JPanel statusBar = LayoutUtil.centerRight(msgBar, progress); 1207 statusBar.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); 1208 return statusBar; 1209 } 1210 1211 /** 1212 * Make the roving progress bar 1213 * 1214 * @return Roving progress bar 1215 */ 1216 public RovingProgress doMakeRovingProgressBar() { 1217 RovingProgress progress = new RovingProgress(Constants.MCV_BLUE) { 1218 private Font labelFont; 1219 1220 public void paintInner(Graphics g) { 1221 //Catch if we're not in a wait state 1222 if (!IdvWindow.getWaitState() && super.isRunning()) { 1223 stop(); 1224 return; 1225 } 1226 if (!super.isRunning()) { 1227 super.paintInner(g); 1228 return; 1229 } 1230 super.paintInner(g); 1231 } 1232 1233 public void paintLabel(Graphics g, Rectangle bounds) { 1234 if (labelFont == null) { 1235 labelFont = g.getFont(); 1236 labelFont = labelFont.deriveFont(Font.BOLD); 1237 } 1238 g.setFont(labelFont); 1239 g.setColor(Color.black); 1240 if (DataSourceImpl.getOutstandingGetDataCalls() > 0) { 1241 g.drawString(" Reading data", 5, bounds.height - 4); 1242 } else if (!idv.getAllDisplaysIntialized()){ 1243 g.drawString(" Creating layers", 5, bounds.height - 4); 1244 } 1245 1246 } 1247 1248 public synchronized void stop() { 1249 super.stop(); 1250 super.reset(); 1251 } 1252 }; 1253 progress.setPreferredSize(new Dimension(130, 10)); 1254 return progress; 1255 } 1256 1257 /** 1258 * Overrides the IDV's getToolbarUI so that McV can return its own toolbar 1259 * and not deal with the way the IDV handles toolbars. This method also 1260 * updates the toolbar data member so that other methods can fool around 1261 * with whatever the IDV thinks is a toolbar (without having to rely on the 1262 * IDV window manager code). 1263 * 1264 * <p> 1265 * Not that the IDV code is bad of course--I just can't handle that pause 1266 * while the toolbar is rebuilt! 1267 * </p> 1268 * 1269 * @return A new toolbar based on the contents of toolbar.xml. 1270 */ 1271 @Override public JComponent getToolbarUI() { 1272 if (toolbars == null) { 1273 toolbars = new LinkedList<JToolBar>(); 1274 } 1275 JToolBar toolbar = new JToolBar(); 1276 populateToolbar(toolbar); 1277 toolbars.add(toolbar); 1278 return toolbar; 1279 } 1280 1281 /** 1282 * Return a McV-style toolbar to the IDV. 1283 * 1284 * @return A fancy-pants toolbar. 1285 */ 1286 @Override protected JComponent doMakeToolbar() { 1287 return getToolbarUI(); 1288 } 1289 1290 /** 1291 * Uses the cached XML to create a toolbar. Any updates to the toolbar 1292 * happen almost instantly using this approach. Do note that if there are 1293 * any components in the given toolbar they will be removed. 1294 * 1295 * @param toolbar A reference to the toolbar that needs buttons and stuff. 1296 */ 1297 private void populateToolbar(JToolBar toolbar) { 1298 // clear out the toolbar's current denizens, if any. just a nicety. 1299 if (toolbar.getComponentCount() > 0) { 1300 toolbar.removeAll(); 1301 } 1302 1303 // ensure that the toolbar popup menu appears 1304 if (toolbarMenu == null) { 1305 toolbarMenu = constructToolbarMenu(); 1306 } 1307 1308 toolbar.addMouseListener(toolbarMenu); 1309 1310 // add the actions that should appear in the toolbar. 1311 for (String action : cachedButtons) { 1312 1313 // null actions are considered separators. 1314 if (action == null) { 1315 toolbar.addSeparator(); 1316 } 1317 // otherwise we've got a button to add 1318 else { 1319 JButton b = buildToolbarButton(action); 1320 if (b != null) { 1321 toolbar.add(b); 1322 } else { 1323 String err = String.format(BAD_ACTION_MSG, action, action); 1324 LogUtil.userErrorMessage(err); 1325 } 1326 } 1327 } 1328 1329 if (getStore().get(Constants.PREF_SHOW_SYSTEM_BUNDLES, true)) { 1330 toolbar.addSeparator(); 1331 1332 BundleTreeNode treeRoot = buildBundleTree(SavedBundle.TYPE_SYSTEM); 1333 if (treeRoot != null) { 1334 1335 // add the favorite bundles to the toolbar (hello Tom Whittaker!) 1336 for (BundleTreeNode tmp : treeRoot.getChildren()) { 1337 1338 // if this node doesn't have a bundle, it's considered a parent 1339 if (tmp.getBundle() == null) { 1340 addBundleTree(toolbar, tmp); 1341 } 1342 // otherwise it's just another button to add. 1343 else { 1344 addBundle(toolbar, tmp); 1345 } 1346 } 1347 } 1348 } 1349 1350 toolbar.addSeparator(); 1351 1352 BundleTreeNode treeRoot = buildBundleTree(SavedBundle.TYPE_FAVORITE); 1353 if (treeRoot != null) { 1354 1355 // add the favorite bundles to the toolbar (hello Tom Whittaker!) 1356 for (BundleTreeNode tmp : treeRoot.getChildren()) { 1357 1358 // if this node doesn't have a bundle, it's considered a parent 1359 if (tmp.getBundle() == null) { 1360 addBundleTree(toolbar, tmp); 1361 } 1362 // otherwise it's just another button to add. 1363 else { 1364 addBundle(toolbar, tmp); 1365 } 1366 } 1367 } 1368 } 1369 1370 /** 1371 * Given a reference to the current toolbar and a bundle tree node, build a 1372 * button representation of the bundle and add it to the toolbar. 1373 * 1374 * @param toolbar Toolbar to which we add the bundle. 1375 * @param node Node within the bundle tree that contains our bundle. 1376 */ 1377 private void addBundle(JToolBar toolbar, BundleTreeNode node) { 1378 final SavedBundle bundle = node.getBundle(); 1379 1380 ImageIcon fileIcon = 1381 GuiUtils.getImageIcon("/auxdata/ui/icons/File.gif"); 1382 1383 JButton button = new JButton(node.getName(), fileIcon); 1384 button.setToolTipText("Click to open favorite: " + node.getName()); 1385 button.addActionListener(e -> { 1386 // running in a separate thread is kinda nice! *HEH* 1387 Misc.run(UIManager.this, "processBundle", bundle); 1388 }); 1389 toolbar.add(button); 1390 } 1391 1392 /** 1393 * Handle loading the given {@link ucar.unidata.idv.SavedBundle SavedBundle}. 1394 * 1395 * <p>Overridden in McIDAS-V to allow {@literal "default"} bundles to 1396 * show the same bundle loading dialog as a {@literal "favorite"} bundle. 1397 * </p> 1398 * 1399 * @param bundle Bundle to process. Cannot be {@code null}. 1400 */ 1401 @Override public void processBundle(SavedBundle bundle) { 1402 showWaitCursor(); 1403 LogUtil.message("Loading bundle: " + bundle.getName()); 1404 int type = bundle.getType(); 1405 boolean checkToRemove = (type == SavedBundle.TYPE_FAVORITE) || (type == SavedBundle.TYPE_SYSTEM); 1406 boolean ok = getPersistenceManager().decodeXmlFile(bundle.getUrl(), 1407 bundle.getName(), 1408 checkToRemove); 1409 if (ok && (bundle.getType() == SavedBundle.TYPE_DATA)) { 1410 showDataSelector(); 1411 } 1412 LogUtil.message(""); 1413 showNormalCursor(); 1414 } 1415 1416 /** 1417 * Builds two things, given a toolbar and a tree node: a JButton that 1418 * represents a {@literal "first-level"} parent node and a JPopupMenu that 1419 * appears upon clicking the JButton. The button is then added to the given 1420 * toolbar. 1421 * 1422 * <p>{@literal "First-level"} means the given node is a child of the root 1423 * node.</p> 1424 * 1425 * @param toolbar Toolbar to which we add the bundle tree. 1426 * @param node Node we want to add. 1427 */ 1428 private void addBundleTree(JToolBar toolbar, BundleTreeNode node) { 1429 ImageIcon catIcon = 1430 GuiUtils.getImageIcon("/auxdata/ui/icons/Folder.gif"); 1431 1432 final JButton button = new JButton(node.getName(), catIcon); 1433 final JPopupMenu popup = new JPopupMenu(); 1434 1435 button.setToolTipText("Show Favorites category: " + node.getName()); 1436 1437 button.addActionListener(new ActionListener() { 1438 public void actionPerformed(ActionEvent e) { 1439 popup.show(button, 0, button.getHeight()); 1440 } 1441 }); 1442 1443 toolbar.add(button); 1444 1445 // recurse through the child nodes 1446 for (BundleTreeNode kid : node.getChildren()) { 1447 buildPopupMenu(kid, popup); 1448 } 1449 } 1450 1451 /** 1452 * Writes the currently displayed toolbar buttons to the toolbar XML. This 1453 * has mostly been ripped off from ToolbarEditor. :( 1454 */ 1455 public void writeToolbar() { 1456 XmlResourceCollection resources = 1457 getResourceManager() 1458 .getXmlResources(IdvResourceManager.RSC_TOOLBAR); 1459 1460 String actionPrefix = "action:"; 1461 1462 // ensure that the IDV can read the XML we're generating. 1463 Document doc = resources.getWritableDocument("<panel/>"); 1464 Element root = resources.getWritableRoot("<panel/>"); 1465 root.setAttribute(XmlUi.ATTR_LAYOUT, XmlUi.LAYOUT_FLOW); 1466 root.setAttribute(XmlUi.ATTR_MARGIN, "4"); 1467 root.setAttribute(XmlUi.ATTR_VSPACE, "0"); 1468 root.setAttribute(XmlUi.ATTR_HSPACE, "2"); 1469 root.setAttribute(XmlUi.inheritName(XmlUi.ATTR_SPACE), "2"); 1470 root.setAttribute(XmlUi.inheritName(XmlUi.ATTR_WIDTH), "5"); 1471 1472 // clear out any pesky kids from previous relationships. XML don't need 1473 // no baby-mama drama. 1474 XmlUtil.removeChildren(root); 1475 1476 // iterate through the actions that have toolbar buttons in use and add 1477 // 'em to the XML. 1478 for (String action : cachedButtons) { 1479 Element e; 1480 if (action != null) { 1481 e = doc.createElement(XmlUi.TAG_BUTTON); 1482 e.setAttribute(XmlUi.ATTR_ACTION, (actionPrefix + action)); 1483 } else { 1484 e = doc.createElement(XmlUi.TAG_FILLER); 1485 e.setAttribute(XmlUi.ATTR_WIDTH, "5"); 1486 } 1487 root.appendChild(e); 1488 } 1489 1490 // write the XML 1491 try { 1492 resources.writeWritable(); 1493 } catch (Exception e) { 1494 e.printStackTrace(); 1495 } 1496 } 1497 1498 /** 1499 * Read the contents of the toolbar XML into a List. We're essentially just 1500 * throwing actions into the list. 1501 * 1502 * @return The actions/buttons that live in the toolbar xml. Note that if 1503 * an element is {@code null}, this element represents a {@literal "space"} 1504 * that should appear in both the Toolbar and the Toolbar Preferences. 1505 */ 1506 public List<String> readToolbar() { 1507 final Element root = getToolbarRoot(); 1508 if (root == null) { 1509 return null; 1510 } 1511 1512 final NodeList elements = XmlUtil.getElements(root); 1513 List<String> data = new ArrayList<>(elements.getLength()); 1514 for (int i = 0; i < elements.getLength(); i++) { 1515 Element child = (Element)elements.item(i); 1516 if (child.getTagName().equals(XmlUi.TAG_BUTTON)) { 1517 data.add( 1518 XmlUtil.getAttribute(child, ATTR_ACTION, (String)null) 1519 .substring(7)); 1520 } else { 1521 data.add(null); 1522 } 1523 } 1524 return data; 1525 } 1526 1527 /** 1528 * Returns the icon associated with {@code actionId}. Note that associating 1529 * the {@literal "missing icon"} icon with an action is allowable. 1530 * 1531 * @param actionId Action ID whose associated icon is to be returned. 1532 * @param style Returned icon's size will be the size associated with the 1533 * specified {@code ToolbarStyle}. 1534 * 1535 * @return Either the icon corresponding to {@code actionId} or the default 1536 * {@literal "missing icon"} icon. 1537 * 1538 * @throws NullPointerException if {@code actionId} is null. 1539 */ 1540 protected Icon getActionIcon(final String actionId, 1541 final ToolbarStyle style) 1542 { 1543 if (actionId == null) 1544 throw new NullPointerException("Action ID cannot be null"); 1545 1546 Icon actionIcon = idvActions.getStyledIconFor(actionId, style); 1547 if (actionIcon != null) 1548 return actionIcon; 1549 1550 String icon = "/edu/wisc/ssec/mcidasv/resources/icons/toolbar/range-bearing%d.png"; 1551 URL tmp = getClass().getResource(String.format(icon, style.getSize())); 1552 return new ImageIcon(tmp); 1553 } 1554 1555 /** 1556 * Returns the known {@link IdvAction}s in the form of {@link IdvActions}. 1557 * 1558 * @return {@link #idvActions} 1559 */ 1560 public IdvActions getCachedActions() { 1561 return idvActions; 1562 } 1563 1564 /** 1565 * Returns the actions that currently make up the McIDAS-V toolbar. 1566 * 1567 * @return {@link List} of {@link ActionAttribute#ID}s that make up the 1568 * current toolbar buttons. 1569 */ 1570 public List<String> getCachedButtons() { 1571 if (cachedButtons == null) { 1572 cachedButtons = readToolbar(); 1573 } 1574 return cachedButtons; 1575 } 1576 1577 /** 1578 * Make the menu of actions. 1579 * 1580 * <p>Overridden in McIDAS-V so that we can fool the IDV into working with 1581 * our icons that allow for multiple {@literal "styles"}. 1582 * 1583 * @param obj Object to call. 1584 * @param method Method to call. 1585 * @param makeCall if {@code true}, call 1586 * {@link IntegratedDataViewer#handleAction(String)}. 1587 * 1588 * @return List of {@link JMenu}s that represent our action menus. 1589 */ 1590 @Override public List<JMenu> makeActionMenu(final Object obj, 1591 final String method, final boolean makeCall) 1592 { 1593 List<JMenu> menu = arrList(); 1594 IdvActions actions = getCachedActions(); 1595 for (String group : actions.getAllGroups()) { 1596 List<JMenuItem> items = arrList(); 1597 for (IdvAction action : actions.getActionsForGroup(group)) { 1598 String cmd = (makeCall) ? action.getCommand() : action.getId(); 1599 String desc = action.getAttribute(ActionAttribute.DESCRIPTION); 1600// items.add(GuiUtils.makeMenuItem(desc, obj, method, cmd)); 1601 items.add(makeMenuItem(desc, obj, method, cmd)); 1602 } 1603// menu.add(GuiUtils.makeMenu(group, items)); 1604 menu.add(makeMenu(group, items)); 1605 } 1606 return menu; 1607 } 1608 1609 /** 1610 * {@inheritDoc} 1611 */ 1612 public static JMenuItem makeMenuItem(String label, Object obj, 1613 String method, Object arg) 1614 { 1615 return MenuUtil.makeMenuItem(label, obj, method, arg); 1616 } 1617 1618 /** 1619 * {@inheritDoc} 1620 */ 1621 @SuppressWarnings("unchecked") 1622 public static JMenu makeMenu(String name, List menuItems) { 1623 return MenuUtil.makeMenu(name, menuItems); 1624 } 1625 1626 /** 1627 * Returns the collection of action identifiers. 1628 * 1629 * <p>Overridden in McIDAS-V so that we can fool the IDV into working with 1630 * our icons that allow for multiple {@literal "styles"}. 1631 * 1632 * @return {@link List} of {@link String}s that correspond to 1633 * {@link IdvAction IdvActions}. 1634 */ 1635 @Override public List<String> getActions() { 1636 return idvActions.getAttributes(ActionAttribute.ID); 1637 } 1638 1639 /** 1640 * Looks for the XML {@link Element} representation of the action 1641 * associated with {@code actionId}. 1642 * 1643 * <p>Overridden in McIDAS-V so that we can fool the IDV into working with 1644 * our icons that allow for multiple {@literal "styles"}. 1645 * 1646 * @param actionId ID of the action whose {@literal "action node"} is desired. Cannot be {@code null}. 1647 * 1648 * @return {@literal "action node"} associated with {@code actionId}. 1649 * 1650 * @throws NullPointerException if {@code actionId} is {@code null}. 1651 */ 1652 @Override public Element getActionNode(final String actionId) { 1653 requireNonNull(actionId, "Null action id strings are invalid"); 1654 return idvActions.getElementForAction(actionId); 1655 } 1656 1657 /** 1658 * Searches for an action identified by a given {@code actionId}, and 1659 * returns the value associated with its {@code attr}. 1660 * 1661 * <p>Overridden in McIDAS-V so that we can fool the IDV into working with 1662 * our icons that allow for multiple {@literal "styles"}. 1663 * 1664 * @param actionId ID of the action whose attribute value is desired. Cannot be {@code null}. 1665 * @param attr The attribute whose value is desired. Cannot be {@code null}. 1666 * 1667 * @return Value associated with the given action and given attribute. 1668 * 1669 * @throws NullPointerException if {@code actionId} or {@code attr} is {@code null}. 1670 */ 1671 @Override public String getActionAttr(final String actionId, 1672 final String attr) 1673 { 1674 requireNonNull(actionId, "Null action id strings are invalid"); 1675 requireNonNull(attr, "Null attributes are invalid"); 1676 ActionAttribute actionAttr = ActionAttribute.valueOf(attr.toUpperCase()); 1677 return idvActions.getAttributeForAction(stripAction(actionId), actionAttr); 1678 } 1679 1680 /** 1681 * Attempts to verify that {@code element} represents a {@literal "valid"} 1682 * IDV action. 1683 * 1684 * @param element {@link Element} to check. {@code null} values permitted, 1685 * but they return {@code false}. 1686 * 1687 * @return {@code true} if {@code element} had all required 1688 * {@link ActionAttribute}s. {@code false} otherwise, or if 1689 * {@code element} is {@code null}. 1690 */ 1691 private static boolean isValidIdvAction(final Element element) { 1692 if (element == null) { 1693 return false; 1694 } 1695 for (ActionAttribute attribute : ActionAttribute.values()) { 1696 if (!attribute.isRequired()) { 1697 continue; 1698 } 1699 if (!XmlUtil.hasAttribute(element, attribute.asIdvString())) { 1700 return false; 1701 } 1702 } 1703 return true; 1704 } 1705 1706 /** 1707 * Builds a {@link Map} of {@link ActionAttribute}s to values for a given 1708 * {@link Element}. If {@code element} does not contain an optional attribute, 1709 * use the attribute's default value. 1710 * 1711 * @param element {@literal "Action node"} of interest. {@code null} 1712 * permitted, but results in an empty {@code Map}. 1713 * 1714 * @return Mapping of {@code ActionAttribute}s to values, or an empty 1715 * {@code Map} if {@code element} is {@code null}. 1716 */ 1717 private static Map<ActionAttribute, String> actionElementToMap( 1718 final Element element) 1719 { 1720 if (element == null) { 1721 return Collections.emptyMap(); 1722 } 1723 // loop through set of action attributes; if element contains attribute "A", add it; return results. 1724 Map<ActionAttribute, String> attrs = new LinkedHashMap<>(); 1725 for (ActionAttribute attribute : ActionAttribute.values()) { 1726 String idvStr = attribute.asIdvString(); 1727 if (XmlUtil.hasAttribute(element, idvStr)) { 1728 attrs.put(attribute, XmlUtil.getAttribute(element, idvStr)); 1729 } else { 1730 attrs.put(attribute, attribute.defaultValue()); 1731 } 1732 } 1733 return attrs; 1734 } 1735 1736 /** 1737 * <p> 1738 * Builds a tree out of the bundles that should appear within the McV 1739 * toolbar. A tree is a nice way to store this data, as the default IDV 1740 * behavior is to act kinda like a file explorer when it displays these 1741 * bundles. 1742 * </p> 1743 * 1744 * <p> 1745 * The tree makes it REALLY easy to replicate the default IDV 1746 * functionality. 1747 * </p> 1748 * 1749 * @param bundleType One of {@link ucar.unidata.idv.SavedBundle#TYPE_FAVORITE}, 1750 * {@link ucar.unidata.idv.SavedBundle#TYPE_DISPLAY}, 1751 * {@link ucar.unidata.idv.SavedBundle#TYPE_DATA} or 1752 * {@link ucar.unidata.idv.SavedBundle#TYPE_SYSTEM}. 1753 * 1754 * @return The root BundleTreeNode for the tree containing toolbar bundles. 1755 */ 1756 public BundleTreeNode buildBundleTree(int bundleType) { 1757 final String TOOLBAR = "Toolbar"; 1758 1759 final List<SavedBundle> bundles = 1760 getPersistenceManager().getBundles(bundleType); 1761 1762 // handy reference to parent nodes; bundle count * 4 seems pretty safe 1763 final Map<String, BundleTreeNode> mapper = new HashMap<>(bundles.size() * 4); 1764 1765 // iterate through all toolbar bundles 1766 for (SavedBundle bundle : bundles) { 1767 String categoryPath = ""; 1768 String grandParentPath; 1769 1770 // build the "path" to the bundle. these paths basically look like 1771 // "Toolbar>category>subcategory>." so "category" is a category of 1772 // toolbar bundles and subcategory is a subcategory of that. The 1773 // IDV will build nice JPopupMenus with everything that appears in 1774 // "category," so McV needs to do the same thing. thus McV needs to 1775 // figure out the complete path to each toolbar bundle! 1776 List<String> categories = (List<String>)bundle.getCategories(); 1777 if (categories == null || categories.isEmpty() || !TOOLBAR.equals(categories.get(0))) { 1778 continue; 1779 } 1780 1781 for (String category : categories) { 1782 grandParentPath = categoryPath; 1783 categoryPath += category + '>'; 1784 1785 if (!mapper.containsKey(categoryPath)) { 1786 BundleTreeNode grandParent = mapper.get(grandParentPath); 1787 BundleTreeNode parent = new BundleTreeNode(category); 1788 if (grandParent != null) { 1789 grandParent.addChild(parent); 1790 } 1791 mapper.put(categoryPath, parent); 1792 } 1793 } 1794 1795 // so the tree book-keeping (if any) is done and we can just add 1796 // the current SavedBundle to its parent node within the tree. 1797 BundleTreeNode parent = mapper.get(categoryPath); 1798 parent.addChild(new BundleTreeNode(bundle.getName(), bundle)); 1799 } 1800 1801 // return the root of the tree. 1802 return mapper.get("Toolbar>"); 1803 } 1804 1805 /** 1806 * Recursively builds the contents of the (first call) JPopupMenu. This is 1807 * where that tree annoyance stuff comes in handy. This is basically a 1808 * simple tree traversal situation. 1809 * 1810 * @param node The node that we're trying to use to build the contents. 1811 * @param comp The component to which we add node contents. 1812 */ 1813 private void buildPopupMenu(BundleTreeNode node, JComponent comp) { 1814 // if the current node has no bundle, it's considered a parent node 1815 if (node.getBundle() == null) { 1816 // parent nodes mean that we have to create a JMenu and add it 1817 JMenu test = new JMenu(node.getName()); 1818 comp.add(test); 1819 1820 // recurse through children to continue building. 1821 for (BundleTreeNode kid : node.getChildren()) { 1822 buildPopupMenu(kid, test); 1823 } 1824 1825 } else { 1826 // nodes with bundles can simply be added to the JMenu 1827 // (or JPopupMenu) 1828 JMenuItem mi = new JMenuItem(node.getName()); 1829 final SavedBundle theBundle = node.getBundle(); 1830 1831 mi.addActionListener(new ActionListener() { 1832 public void actionPerformed(ActionEvent ae) { 1833 //Do it in a thread 1834 Misc.run(UIManager.this, "processBundle", theBundle); 1835 } 1836 }); 1837 1838 comp.add(mi); 1839 } 1840 } 1841 1842 @Override public void initDone() { 1843 super.initDone(); 1844 if (getStore().get(Constants.PREF_VERSION_CHECK, true)) { 1845 StateManager stateManager = (StateManager)getStateManager(); 1846 stateManager.checkForNewerVersion(false); 1847 stateManager.checkForNotice(false); 1848 } 1849 1850 // not super excited about how this works. 1851 // showBasicWindow(true); 1852 1853 initDone = true; 1854 showDashboard(); 1855 } 1856 1857 /** 1858 * Create the splash screen if needed 1859 */ 1860 public void initSplash() { 1861 if (getProperty(PROP_SHOWSPLASH, true) 1862 && !getArgsManager().getNoGui() 1863 && !getArgsManager().getIsOffScreen() 1864 && !getArgsManager().testMode) { 1865 splash = new McvSplash(idv); 1866 splashMsg("Loading Programs"); 1867 } 1868 } 1869 1870 /** 1871 * Create (if null) and show the HelpTipDialog. If checkPrefs is true 1872 * then only create the dialog if the PREF_HELPTIPSHOW preference is true. 1873 * 1874 * @param checkPrefs Should the user preferences be checked 1875 */ 1876 /** THe help tip dialog */ 1877 private McvHelpTipDialog helpTipDialog; 1878 1879 public void initHelpTips(boolean checkPrefs) { 1880 try { 1881 if (getIdv().getArgsManager().getIsOffScreen()) { 1882 return; 1883 } 1884 if (checkPrefs) { 1885 if (!getStore().get(McvHelpTipDialog.PREF_HELPTIPSHOW, true)) { 1886 return; 1887 } 1888 } 1889 if (helpTipDialog == null) { 1890 IdvResourceManager resourceManager = getResourceManager(); 1891 helpTipDialog = new McvHelpTipDialog( 1892 resourceManager.getXmlResources( 1893 resourceManager.RSC_HELPTIPS), getIdv(), getStore(), 1894 getIdvClass(), 1895 getStore().get( 1896 McvHelpTipDialog.PREF_HELPTIPSHOW, true)); 1897 } 1898 helpTipDialog.setVisible(true); 1899 GuiUtils.toFront(helpTipDialog); 1900 } catch (Throwable excp) { 1901 logException("Reading help tips", excp); 1902 } 1903 } 1904 1905 /** 1906 * If created, close the HelpTipDialog window. 1907 */ 1908 public void closeHelpTips() { 1909 if (helpTipDialog != null) { 1910 helpTipDialog.setVisible(false); 1911 } 1912 } 1913 1914 /** 1915 * Create (if null) and show the HelpTipDialog 1916 */ 1917 public void showHelpTips() { 1918 initHelpTips(false); 1919 } 1920 1921 /** 1922 * Populate a menu with bundles known to the {@code PersistenceManager}. 1923 * 1924 * @param inBundleMenu The menu to populate 1925 */ 1926 public void makeBundleMenu(JMenu inBundleMenu) { 1927 final int bundleType = IdvPersistenceManager.BUNDLES_FAVORITES; 1928 1929 JMenuItem mi; 1930 mi = new JMenuItem("Manage..."); 1931 McVGuiUtils.setMenuImage(mi, Constants.ICON_FAVORITEMANAGE_SMALL); 1932 mi.setMnemonic(GuiUtils.charToKeyCode("M")); 1933 inBundleMenu.add(mi); 1934 mi.addActionListener(ae -> showBundleDialog(bundleType)); 1935 1936 final List bundles = getPersistenceManager().getBundles(bundleType); 1937 if (bundles.isEmpty()) { 1938 return; 1939 } 1940 final String title = 1941 getPersistenceManager().getBundleTitle(bundleType); 1942 final String bundleDir = 1943 getPersistenceManager().getBundleDirectory(bundleType); 1944 1945 JMenu bundleMenu = new JMenu(title); 1946 McVGuiUtils.setMenuImage(bundleMenu, Constants.ICON_FAVORITE_SMALL); 1947 bundleMenu.setMnemonic(GuiUtils.charToKeyCode(title)); 1948 1949// getPersistenceManager().initBundleMenu(bundleType, bundleMenu); 1950 1951 Hashtable catMenus = new Hashtable(); 1952 inBundleMenu.addSeparator(); 1953 inBundleMenu.add(bundleMenu); 1954 for (int i = 0; i < bundles.size(); i++) { 1955 SavedBundle bundle = (SavedBundle) bundles.get(i); 1956 List categories = bundle.getCategories(); 1957 JMenu catMenu = bundleMenu; 1958 String mainCategory = ""; 1959 for (int catIdx = 0; catIdx < categories.size(); catIdx++) { 1960 String category = (String) categories.get(catIdx); 1961 mainCategory += "." + category; 1962 JMenu tmpMenu = (JMenu) catMenus.get(mainCategory); 1963 if (tmpMenu == null) { 1964 tmpMenu = new JMenu(category); 1965 catMenu.add(tmpMenu); 1966 catMenus.put(mainCategory, tmpMenu); 1967 } 1968 catMenu = tmpMenu; 1969 } 1970 1971 final SavedBundle theBundle = bundle; 1972 mi = new JMenuItem(bundle.getName()); 1973 mi.addActionListener(new ActionListener() { 1974 public void actionPerformed(ActionEvent ae) { 1975 //Do it in a thread 1976 Misc.run(UIManager.this, "processBundle", theBundle); 1977 } 1978 }); 1979 catMenu.add(mi); 1980 } 1981 } 1982 1983 /** 1984 * Overridden to build a custom Window menu. 1985 * @see ucar.unidata.idv.ui.IdvUIManager#makeWindowsMenu(JMenu, IdvWindow) 1986 */ 1987 @Override public void makeWindowsMenu(final JMenu windowMenu, final IdvWindow idvWindow) { 1988 JMenuItem mi; 1989 boolean first = true; 1990 1991 mi = new JMenuItem("Show Data Explorer"); 1992 McVGuiUtils.setMenuImage(mi, Constants.ICON_DATAEXPLORER_SMALL); 1993 mi.addActionListener(this); 1994 mi.setActionCommand(ACT_SHOW_DASHBOARD); 1995 windowMenu.add(mi); 1996 1997 makeTabNavigationMenu(windowMenu); 1998 1999 @SuppressWarnings("unchecked") // it's how the IDV does it. 2000 List windows = new ArrayList(IdvWindow.getWindows()); 2001 for (int i = 0; i < windows.size(); i++) { 2002 final IdvWindow window = (IdvWindow)windows.get(i); 2003 2004 // Skip the main window 2005 if (window.getIsAMainWindow()) { 2006 continue; 2007 } 2008 2009 String title = window.getTitle(); 2010 String titleParts[] = splitTitle(title); 2011 2012 if (titleParts.length == 2) { 2013 title = titleParts[1]; 2014 } 2015 2016 // Skip the data explorer and display controller 2017 String dataSelectorNameParts[] = splitTitle(Constants.DATASELECTOR_NAME); 2018 if (title.equals(Constants.DATASELECTOR_NAME) || title.equals(dataSelectorNameParts[1])) { 2019 continue; 2020 } 2021 2022 // Add a meaningful name if there is none 2023 if (title.isEmpty()) { 2024 title = "<Unnamed>"; 2025 } 2026 2027 if (window.isVisible()) { 2028 mi = new JMenuItem(title); 2029 mi.addActionListener(ae -> window.toFront()); 2030 2031 if (first) { 2032 windowMenu.addSeparator(); 2033 first = false; 2034 } 2035 2036 windowMenu.add(mi); 2037 } 2038 } 2039 Msg.translateTree(windowMenu); 2040 } 2041 2042 /** 2043 * Add tab navigation {@link JMenuItem JMenuItems} to the given 2044 * {@code menu}. 2045 * 2046 * @param menu Menu to which tab navigation menu items should be added. 2047 * Cannot be {@code null}. 2048 */ 2049 private void makeTabNavigationMenu(final JMenu menu) { 2050 if (!didInitActions) { 2051 didInitActions = true; 2052 initTabNavActions(); 2053 } 2054 2055 if (McVGuiUtils.getAllComponentHolders().size() <= 1) { 2056 return; 2057 } 2058 2059 menu.addSeparator(); 2060 2061 menu.add(new JMenuItem(nextDisplayAction)); 2062 menu.add(new JMenuItem(prevDisplayAction)); 2063 menu.add(new JMenuItem(showDisplayAction)); 2064 2065 if (!McVGuiUtils.getAllComponentGroups().isEmpty()) { 2066 menu.addSeparator(); 2067 } 2068 2069 Msg.translateTree(menu); 2070 } 2071 2072 /** 2073 * Add in the dynamic menu for displaying formulas 2074 * 2075 * @param menu edit menu to add to 2076 */ 2077 public void makeFormulasMenu(JMenu menu) { 2078 MenuUtil.makeMenu(menu, getJythonManager().doMakeFormulaDataSourceMenuItems(null)); 2079 } 2080 2081 /** Whether or not the list of available actions has been initialized. */ 2082 private boolean didInitActions = false; 2083 2084 /** Key combo for the popup with list of displays. */ 2085 private ShowDisplayAction showDisplayAction; 2086 2087 /** 2088 * Key combo for moving to the previous display relative to the current. For 2089 * key combos the lists of displays in the current window is circular. 2090 */ 2091 private PrevDisplayAction prevDisplayAction; 2092 2093 /** 2094 * Key combo for moving to the next display relative to the current. For 2095 * key combos the lists of displays in the current window is circular. 2096 */ 2097 private NextDisplayAction nextDisplayAction; 2098 2099 /** Modifier key, like {@literal "control"} or {@literal "shift"}. */ 2100 private static final String PROP_KB_MODIFIER = "mcidasv.tabbedui.display.kbmodifier"; 2101 2102 /** Key that pops up the list of displays. Used in conjunction with {@code PROP_KB_MODIFIER}. */ 2103 private static final String PROP_KB_SELECT_DISPLAY = "mcidasv.tabbedui.display.kbselect"; 2104 2105 /** Key for moving to the previous display. Used in conjunction with {@code PROP_KB_MODIFIER}. */ 2106 private static final String PROP_KB_DISPLAY_PREV = "mcidasv.tabbedui.display.kbprev"; 2107 2108 /** Key for moving to the next display. Used in conjunction with {@code PROP_KB_MODIFIER}. */ 2109 private static final String PROP_KB_DISPLAY_NEXT = "mcidasv.tabbedui.display.kbnext"; 2110 2111 /** Key for showing the dashboard. Used in conjunction with {@code PROP_KB_MODIFIER}. */ 2112 private static final String PROP_KB_SHOW_DASHBOARD = "mcidasv.tabbedui.display.kbdashboard"; 2113 2114 /** Key for showing the Jython Shell. Used in conjunction with {@code PROP_KB_MODIFIER}. */ 2115 private static final String PROP_KB_SHOW_SHELL = "mcidasv.tabbedui.display.kjythonshell"; 2116 2117 /** Key for showing the Jython Library (modifier is {@literal "alt"} key). */ 2118 private static final String PROP_KB_SHOW_LIBRARY = "mcidasv.tabbedui.display.kjythonlibrary"; 2119 2120 // TODO: make all this stuff static: mod + acc don't need to read the properties file. 2121 // look at: http://community.livejournal.com/jkff_en/341.html 2122 // look at: effective java, particularly the stuff about enums 2123 private void initTabNavActions() { 2124 String mod = idv.getProperty(PROP_KB_MODIFIER, "control") + " "; 2125 String acc = idv.getProperty(PROP_KB_SELECT_DISPLAY, "L"); 2126 2127 String stroke = mod + acc; 2128 showDisplayAction = new ShowDisplayAction(KeyStroke.getKeyStroke(stroke)); 2129 2130 acc = idv.getProperty(PROP_KB_DISPLAY_PREV, "P"); 2131 stroke = mod + acc; 2132 prevDisplayAction = new PrevDisplayAction(KeyStroke.getKeyStroke(stroke)); 2133 2134 acc = idv.getProperty(PROP_KB_DISPLAY_NEXT, "N"); 2135 stroke = mod + acc; 2136 nextDisplayAction = new NextDisplayAction(KeyStroke.getKeyStroke(stroke)); 2137 } 2138 2139 /** 2140 * Add all the show window keyboard shortcuts. To make keyboard shortcuts 2141 * global, i.e., available no matter what window is active, the appropriate 2142 * actions have to be added the the window contents action and input maps. 2143 * 2144 * FIXME: This can't be the right way to do this! 2145 * 2146 * @param window IdvWindow that requires keyboard shortcut capability. 2147 */ 2148 private void initDisplayShortcuts(IdvWindow window) { 2149 //mjh aug2014 make sure showDisplayAction etc. are initialized: 2150 initTabNavActions(); 2151 didInitActions = true; 2152 2153 JComponent jcomp = window.getContents(); 2154 jcomp.getActionMap().put("show_disp", showDisplayAction); 2155 jcomp.getActionMap().put("prev_disp", prevDisplayAction); 2156 jcomp.getActionMap().put("next_disp", nextDisplayAction); 2157 jcomp.getActionMap().put("show_dashboard", new AbstractAction() { 2158 private static final long serialVersionUID = -364947940824325949L; 2159 public void actionPerformed(ActionEvent evt) { 2160 showDashboard(); 2161 } 2162 }); 2163 jcomp.getActionMap().put("show_jython_shell", new AbstractAction() { 2164 @Override public void actionPerformed(ActionEvent e) { 2165 getJythonManager().createShell(); 2166 } 2167 }); 2168 jcomp.getActionMap().put("show_jython_library", new AbstractAction() { 2169 @Override public void actionPerformed(ActionEvent e) { 2170 getJythonManager().showJythonEditor(); 2171 } 2172 }); 2173 2174 String mod = getIdv().getProperty(PROP_KB_MODIFIER, "control"); 2175 String acc = getIdv().getProperty(PROP_KB_SELECT_DISPLAY, "L"); 2176 jcomp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 2177 KeyStroke.getKeyStroke(mod + ' ' + acc), 2178 "show_disp" 2179 ); 2180 2181 acc = getIdv().getProperty(PROP_KB_SHOW_DASHBOARD, "MINUS"); 2182 jcomp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 2183 KeyStroke.getKeyStroke(mod + ' ' + acc), 2184 "show_dashboard" 2185 ); 2186 2187 acc = getIdv().getProperty(PROP_KB_DISPLAY_NEXT, "N"); 2188 jcomp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 2189 KeyStroke.getKeyStroke(mod + ' ' + acc), 2190 "next_disp" 2191 ); 2192 2193 acc = getIdv().getProperty(PROP_KB_DISPLAY_PREV, "P"); 2194 jcomp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 2195 KeyStroke.getKeyStroke(mod + ' ' + acc), 2196 "prev_disp" 2197 ); 2198 2199 acc = getIdv().getProperty(PROP_KB_SHOW_SHELL, "J"); 2200 jcomp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 2201 KeyStroke.getKeyStroke(mod + ' ' + acc), 2202 "show_jython_shell"); 2203 2204 mod = "alt"; 2205 acc = getIdv().getProperty(PROP_KB_SHOW_LIBRARY, "J"); 2206 jcomp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 2207 KeyStroke.getKeyStroke(mod + ' ' + acc), 2208 "show_jython_library"); 2209 } 2210 2211 /** 2212 * Show Bruce's display selector widget. 2213 */ 2214 protected void showDisplaySelector() { 2215 IdvWindow mainWindow = IdvWindow.getActiveWindow(); 2216 JPanel contents = new JPanel(); 2217 contents.setLayout(new BorderLayout()); 2218 JComponent comp = getDisplaySelectorComponent(); 2219 final JDialog dialog = new JDialog(mainWindow.getFrame(), "List Displays", true); 2220 dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 2221 contents.add(comp, BorderLayout.CENTER); 2222 JButton button = new JButton("OK"); 2223 button.addActionListener(new ActionListener() { 2224 public void actionPerformed(ActionEvent evt) { 2225 final ViewManager vm = getVMManager().getLastActiveViewManager(); 2226 // final DisplayProps disp = getDisplayProps(vm); 2227 // if (disp != null) 2228 // showDisplay(disp); 2229 final McvComponentHolder holder = (McvComponentHolder)getViewManagerHolder(vm); 2230 if (holder != null) { 2231 holder.setAsActiveTab(); 2232 } 2233 2234 // have to do this on the event dispatch thread so we make 2235 // sure it happens after showDisplay 2236 SwingUtilities.invokeLater(new Runnable() { 2237 public void run() { 2238 //setActiveDisplay(disp, disp.managers.indexOf(vm)); 2239 if (holder != null) { 2240 getVMManager().setLastActiveViewManager(vm); 2241 } 2242 } 2243 }); 2244 2245 dialog.dispose(); 2246 } 2247 }); 2248 JPanel buttonPanel = new JPanel(); 2249 buttonPanel.add(button); 2250 dialog.add(buttonPanel, BorderLayout.AFTER_LAST_LINE); 2251 JScrollPane scroller = new JScrollPane(contents); 2252 scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); 2253 scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); 2254 dialog.add(scroller, BorderLayout.CENTER); 2255 dialog.setSize(200, 300); 2256 dialog.setLocationRelativeTo(mainWindow.getFrame()); 2257 dialog.setVisible(true); 2258 } 2259 2260 private class ShowDisplayAction extends AbstractAction { 2261 private static final long serialVersionUID = -4609753725057124244L; 2262 private static final String ACTION_NAME = "List Displays..."; 2263 public ShowDisplayAction(KeyStroke k) { 2264 super(ACTION_NAME); 2265 putValue(Action.ACCELERATOR_KEY, k); 2266 } 2267 2268 public void actionPerformed(ActionEvent e) { 2269 showDisplaySelector(); 2270 } 2271 } 2272 2273 private class PrevDisplayAction extends AbstractAction { 2274 private static final long serialVersionUID = -3551890663976755671L; 2275 private static final String ACTION_NAME = "Previous Display"; 2276 2277 public PrevDisplayAction(KeyStroke k) { 2278 super(ACTION_NAME); 2279 putValue(Action.ACCELERATOR_KEY, k); 2280 } 2281 2282 public void actionPerformed(ActionEvent e) { 2283 McvComponentHolder prev = (McvComponentHolder)McVGuiUtils.getBeforeActiveHolder(); 2284 if (prev != null) { 2285 prev.setAsActiveTab(); 2286 } 2287 } 2288 } 2289 2290 private class NextDisplayAction extends AbstractAction { 2291 private static final long serialVersionUID = 5431901451767117558L; 2292 private static final String ACTION_NAME = "Next Display"; 2293 2294 public NextDisplayAction(KeyStroke k) { 2295 super(ACTION_NAME); 2296 putValue(Action.ACCELERATOR_KEY, k); 2297 } 2298 2299 public void actionPerformed(ActionEvent e) { 2300 McvComponentHolder next = (McvComponentHolder)McVGuiUtils.getAfterActiveHolder(); 2301 if (next != null) { 2302 next.setAsActiveTab(); 2303 } 2304 } 2305 } 2306 2307 /** 2308 * Populate a "new display" menu from the available skin list. Many thanks 2309 * to Bruce for doing this in the venerable TabbedUIManager. 2310 * 2311 * @param newDisplayMenu menu to populate. 2312 * @param inWindow Is the skinned display to be created in a window? 2313 * 2314 * @see ucar.unidata.idv.IdvResourceManager#RSC_SKIN 2315 * 2316 * @return Menu item populated with display skins 2317 */ 2318 protected JMenuItem doMakeNewDisplayMenu(JMenuItem newDisplayMenu, 2319 final boolean inWindow) 2320 { 2321 if (newDisplayMenu != null) { 2322 2323 String skinFilter = "idv.skin"; 2324 if (!inWindow) { 2325 skinFilter = "mcv.skin"; 2326 } 2327 2328 final XmlResourceCollection skins = 2329 getResourceManager().getXmlResources( 2330 IdvResourceManager.RSC_SKIN); 2331 2332 Map<String, JMenu> menus = new Hashtable<>(); 2333 for (int i = 0; i < skins.size(); i++) { 2334 final Element root = skins.getRoot(i); 2335 if (root == null) { 2336 continue; 2337 } 2338 2339 // filter out mcv or idv skins based on whether or not we're 2340 // interested in tabs or new windows. 2341 final String skinid = skins.getProperty("skinid", i); 2342 if ((skinid != null) && skinid.startsWith(skinFilter)) { 2343 continue; 2344 } 2345 2346 final int skinIndex = i; 2347 List<String> names = 2348 StringUtil.split(skins.getShortName(i), ">", true, true); 2349 2350 JMenuItem theMenu = newDisplayMenu; 2351 String path = ""; 2352 for (int nameIdx = 0; nameIdx < names.size() - 1; nameIdx++) { 2353 String catName = names.get(nameIdx); 2354 path = path + '>' + catName; 2355 JMenu tmpMenu = menus.get(path); 2356 if (tmpMenu == null) { 2357 tmpMenu = new JMenu(catName); 2358 theMenu.add(tmpMenu); 2359 menus.put(path, tmpMenu); 2360 } 2361 theMenu = tmpMenu; 2362 } 2363 2364 final String name = names.get(names.size() - 1); 2365 2366 IdvWindow window = IdvWindow.getActiveWindow(); 2367 for (final McvComponentGroup group : McVGuiUtils.idvGroupsToMcv(window)) { 2368 JMenuItem mi = new JMenuItem(name); 2369 2370 mi.addActionListener(ae -> { 2371 if (!inWindow) { 2372 createNewTab(skinid); 2373 } else { 2374 createNewWindow(null, true, 2375 getStateManager().getTitle(), skins.get( 2376 skinIndex).toString(), skins.getRoot( 2377 skinIndex, false), inWindow, null); 2378 } 2379 }); 2380 theMenu.add(mi); 2381 } 2382 } 2383 2384 // attach the dynamic skin menu item to the tab menu. 2385// if (!inWindow) { 2386// ((JMenu)newDisplayMenu).addSeparator(); 2387// IdvWindow window = IdvWindow.getActiveWindow(); 2388// 2389// final McvComponentGroup group = 2390// (McvComponentGroup)window.getComponentGroups().get(0); 2391// 2392// JMenuItem mi = new JMenuItem("Choose Your Own Adventure..."); 2393// mi.addActionListener(new ActionListener() { 2394// 2395// public void actionPerformed(ActionEvent e) { 2396// makeDynamicSkin(group); 2397// } 2398// }); 2399// newDisplayMenu.add(mi); 2400// } 2401 } 2402 return newDisplayMenu; 2403 } 2404 2405 // for the time being just create some basic viewmanagers. 2406// public void makeDynamicSkin(McvComponentGroup group) { 2407// // so I have my megastring (which I hate--a class that can generate XML would be cooler) (though it would boil down to the same thing...) 2408// try { 2409// Document doc = XmlUtil.getDocument(SKIN_TEMPLATE); 2410// Element root = doc.getDocumentElement(); 2411// Element rightChild = doc.createElement("idv.view"); 2412// rightChild.setAttribute("class", "ucar.unidata.idv.TransectViewManager"); 2413// rightChild.setAttribute("viewid", "viewright1337"); 2414// rightChild.setAttribute("id", "viewright"); 2415// rightChild.setAttribute("properties", "name=Panel 1;clickToFocus=true;showToolBars=true;shareViews=true;showControlLegend=false;initialSplitPaneLocation=0.2;legendOnLeft=true;size=300:400;shareGroup=view%versionuid%;"); 2416// 2417// Element leftChild = doc.createElement("idv.view"); 2418// leftChild.setAttribute("class", "ucar.unidata.idv.MapViewManager"); 2419// leftChild.setAttribute("viewid", "viewleft1337"); 2420// leftChild.setAttribute("id", "viewleft"); 2421// leftChild.setAttribute("properties", "name=Panel 2;clickToFocus=true;showToolBars=true;shareViews=true;showControlLegend=false;size=300:400;shareGroup=view%versionuid%;"); 2422// 2423// Element startNode = XmlUtil.findElement(root, "splitpane", "embeddednode", "true"); 2424// startNode.appendChild(rightChild); 2425// startNode.appendChild(leftChild); 2426// group.makeDynamicSkin(root); 2427// } catch (Exception e) { 2428// LogUtil.logException("Error: parsing skin template:", e); 2429// } 2430// } 2431// 2432// private static final String SKIN_TEMPLATE = 2433// "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + 2434// "<skin embedded=\"true\">\n" + 2435// " <ui>\n" + 2436// " <panel layout=\"border\" bgcolor=\"red\">\n" + 2437// " <idv.menubar place=\"North\"/>\n" + 2438// " <panel layout=\"border\" place=\"Center\">\n" + 2439// " <panel layout=\"flow\" place=\"North\">\n" + 2440// " <idv.toolbar id=\"idv.toolbar\" place=\"West\"/>\n" + 2441// " <panel id=\"idv.favoritesbar\" place=\"North\"/>\n" + 2442// " </panel>\n" + 2443// " <splitpane embeddednode=\"true\" resizeweight=\"0.5\" onetouchexpandable=\"true\" orientation=\"h\" bgcolor=\"blue\" layout=\"grid\" cols=\"2\" place=\"Center\">\n" + 2444// " </splitpane>\n" + 2445// " </panel>\n" + 2446// " <component idref=\"bottom_bar\"/>\n" + 2447// " </panel>\n" + 2448// " </ui>\n" + 2449// " <styles>\n" + 2450// " <style class=\"iconbtn\" space=\"2\" mouse_enter=\"ui.setText(idv.messagelabel,prop:tooltip);ui.setBorder(this,etched);\" mouse_exit=\"ui.setText(idv.messagelabel,);ui.setBorder(this,button);\"/>\n" + 2451// " <style class=\"textbtn\" space=\"2\" mouse_enter=\"ui.setText(idv.messagelabel,prop:tooltip)\" mouse_exit=\"ui.setText(idv.messagelabel,)\"/>\n" + 2452// " </styles>\n" + 2453// " <components>\n" + 2454// " <idv.statusbar place=\"South\" id=\"bottom_bar\"/>\n" + 2455// " </components>\n" + 2456// " <properties>\n" + 2457// " <property name=\"icon.wait.wait\" value=\"/ucar/unidata/idv/images/wait.gif\"/>\n" + 2458// " </properties>\n" + 2459// "</skin>\n"; 2460 2461 private int holderCount; 2462 2463 /** 2464 * Associates a given ViewManager with a given ComponentHolder. 2465 * 2466 * @param vm The ViewManager that is inside {@code holder}. 2467 * @param holder The ComponentHolder that contains {@code vm}. 2468 */ 2469 public void setViewManagerHolder(ViewManager vm, ComponentHolder holder) { 2470 viewManagers.put(vm, holder); 2471 holderCount = getComponentHolders().size(); 2472 } 2473 2474 public Set<ComponentHolder> getComponentHolders() { 2475 return newHashSet(viewManagers.values()); 2476 } 2477 2478 public int getComponentHolderCount() { 2479 return holderCount; 2480 } 2481 2482 public int getComponentGroupCount() { 2483 return getComponentGroups().size(); 2484 } 2485 2486 /** 2487 * Returns the ComponentHolder containing the given ViewManager. 2488 * 2489 * @param vm The ViewManager whose ComponentHolder is needed. 2490 * 2491 * @return Either {@code null} or the {@code ComponentHolder}. 2492 */ 2493 public ComponentHolder getViewManagerHolder(ViewManager vm) { 2494 return viewManagers.get(vm); 2495 } 2496 2497 /** 2498 * Disassociate a given {@code ViewManager} from its 2499 * {@code ComponentHolder}. 2500 * 2501 * @param vm {@code ViewManager} to disassociate. 2502 * 2503 * @return The associated {@code ComponentHolder}. 2504 */ 2505 public ComponentHolder removeViewManagerHolder(ViewManager vm) { 2506 ComponentHolder holder = viewManagers.remove(vm); 2507 holderCount = getComponentHolders().size(); 2508 return holder; 2509 } 2510 2511 /** 2512 * Overridden to keep the dashboard around after it's initially created. 2513 * Also give the user the ability to show a particular tab. 2514 * 2515 * @see ucar.unidata.idv.ui.IdvUIManager#showDashboard() 2516 */ 2517 @Override public void showDashboard() { 2518 showDashboard(""); 2519 } 2520 2521 /** 2522 * Creates the {@link McIDASVViewPanel} component that shows up in the 2523 * dashboard. 2524 * 2525 * @return McIDAS-V specific view panel. 2526 */ 2527 @Override protected ViewPanel doMakeViewPanel() { 2528 ViewPanel vp = new McIDASVViewPanel(idv); 2529 vp.getContents(); 2530 return vp; 2531 } 2532 2533 /** 2534 * Build a mapping of {@literal "skin"} IDs to their indicies within skin 2535 * resources. 2536 * 2537 * @return Map of skin ids to their index within the skin resource. 2538 */ 2539 private Map<String, Integer> readSkinIds() { 2540 XmlResourceCollection skins = 2541 getResourceManager().getXmlResources(IdvResourceManager.RSC_SKIN); 2542 Map<String, Integer> ids = new HashMap<>(skins.size()); 2543 for (int i = 0; i < skins.size(); i++) { 2544 String id = skins.getProperty("skinid", i); 2545 if (id != null) { 2546 ids.put(id, i); 2547 } 2548 } 2549 return ids; 2550 } 2551 2552 /** 2553 * Adds a skinned component holder to the active component group. 2554 * 2555 * @param skinId The value of the skin's skinid attribute. 2556 */ 2557 public void createNewTab(final String skinId) { 2558 IdvWindow activeWindow = IdvWindow.getActiveWindow(); 2559 IdvComponentGroup group = 2560 McVGuiUtils.getComponentGroup(activeWindow); 2561 if (skinIds.containsKey(skinId)) { 2562 group.makeSkin(skinIds.get(skinId)); 2563 } 2564 JFrame frame = activeWindow.getFrame(); 2565 if (frame != null) { 2566 frame.setPreferredSize(frame.getSize()); 2567 } 2568 } 2569 2570 /** 2571 * Method to do the work of showing the Data Explorer (nee Dashboard). 2572 * 2573 * @param tabName Name of the tab that should be made active. 2574 * Cannot be {@code null}, but empty {@code String} values 2575 * will not change the active tab. 2576 */ 2577 @SuppressWarnings("unchecked") // IdvWindow.getWindows only adds IdvWindows. 2578 public void showDashboard(String tabName) { 2579 if (!initDone) { 2580 return; 2581 } else if (dashboard == null) { 2582 showWaitCursor(); 2583 doMakeBasicWindows(); 2584 showNormalCursor(); 2585 String title = makeTitle(getStateManager().getTitle(), Constants.DATASELECTOR_NAME); 2586 for (IdvWindow window : (List<IdvWindow>)IdvWindow.getWindows()) { 2587 if (title.equals(window.getTitle())) { 2588 dashboard = window; 2589 dashboard.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); 2590 } 2591 } 2592 } else { 2593 dashboard.show(); 2594 } 2595 2596 if (tabName.isEmpty()) { 2597 return; 2598 } 2599 2600 // Dig two panels deep looking for a JTabbedPane 2601 // If you find one, try to show the requested tab name 2602 JComponent contents = dashboard.getContents(); 2603 JComponent component = (JComponent)contents.getComponent(0); 2604 JTabbedPane tPane = null; 2605 if (component instanceof JTabbedPane) { 2606 tPane = (JTabbedPane)component; 2607 } 2608 else { 2609 JComponent component2 = (JComponent)component.getComponent(0); 2610 if (component2 instanceof JTabbedPane) { 2611 tPane = (JTabbedPane)component2; 2612 } 2613 } 2614 if (tPane != null) { 2615 for (int i=0; i<tPane.getTabCount(); i++) { 2616 if (tabName.equals(tPane.getTitleAt(i))) { 2617 tPane.setSelectedIndex(i); 2618 break; 2619 } 2620 } 2621 } 2622 } 2623 2624 /** 2625 * Show the support request form 2626 * 2627 * @param description Default value for the description form entry 2628 * @param stackTrace The stack trace that caused this error. 2629 * @param dialog The dialog to put the gui in, if non-null. 2630 */ 2631 public void showSupportForm(final String description, 2632 final String stackTrace, final JDialog dialog) 2633 { 2634 java.awt.EventQueue.invokeLater(() -> { 2635 // TODO: mcvstatecollector should have a way to gather the 2636 // exception information.. 2637 McIDASV mcv = (McIDASV)getIdv(); 2638 new SupportForm(getStore(), new McvStateCollector(mcv)).setVisible(true); 2639 }); 2640 } 2641 2642 /** 2643 * Attempts to locate and display a dashboard component using an ID. 2644 * 2645 * @param id ID of the desired component. 2646 * 2647 * @return True if {@code id} corresponds to a component. False otherwise. 2648 */ 2649 public boolean showDashboardComponent(String id) { 2650 Object comp = findComponent(id); 2651 if (comp != null) { 2652 GuiUtils.showComponentInTabs((JComponent)comp); 2653 return true; 2654 } else { 2655 super.showDashboard(); 2656 for (IdvWindow window : (List<IdvWindow>)IdvWindow.getWindows()) { 2657 String title = makeTitle( 2658 getStateManager().getTitle(), 2659 Constants.DATASELECTOR_NAME 2660 ); 2661 if (title.equals(window.getTitle())) { 2662 dashboard = window; 2663 dashboard.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); 2664 } 2665 } 2666 } 2667 return false; 2668 } 2669 2670 /** 2671 * Close and dispose of the splash window (if it has been created). 2672 */ 2673 @Override 2674 public void splashClose() { 2675 if (splash != null) { 2676 splash.doClose(); 2677 } 2678 } 2679 2680 /** 2681 * Show a message in the splash screen (if it exists) 2682 * 2683 * @param m The message to show 2684 */ 2685 @Override public void splashMsg(String m) { 2686 if (splash != null) { 2687 splash.splashMsg(m); 2688 } 2689 } 2690 2691 /** 2692 * Uses a given toolbar editor to repopulate all toolbars so that they 2693 * correspond to the user's choice of actions. 2694 * 2695 * @param tbe The toolbar editor that contains the actions the user wants. 2696 */ 2697 public void setCurrentToolbars(final McvToolbarEditor tbe) { 2698 List<TwoFacedObject> tfos = tbe.getTLP().getCurrentEntries(); 2699 List<String> buttonIds = new ArrayList<>(tfos.size()); 2700 for (TwoFacedObject tfo : tfos) { 2701 if (McvToolbarEditor.isSpace(tfo)) { 2702 buttonIds.add(null); 2703 } else { 2704 buttonIds.add(TwoFacedObject.getIdString(tfo)); 2705 } 2706 } 2707 2708 cachedButtons = buttonIds; 2709 2710 for (JToolBar toolbar : toolbars) { 2711 toolbar.setVisible(false); 2712 populateToolbar(toolbar); 2713 toolbar.setVisible(true); 2714 } 2715 } 2716 2717 /** 2718 * Append a string and object to the buffer 2719 * 2720 * @param sb StringBuffer to append to 2721 * @param name Name of the object 2722 * @param value the object value 2723 */ 2724 private void append(StringBuffer sb, String name, Object value) { 2725 sb.append("<b>").append(name).append("</b>: ").append(value).append("<br>"); 2726 } 2727 2728 private JMenuItem makeControlDescriptorItem(ControlDescriptor cd) { 2729 JMenuItem mi = new JMenuItem(); 2730 if (cd != null) { 2731 mi = new JMenuItem(cd.getLabel()); 2732 mi.addActionListener(new ObjectListener(cd) { 2733 public void actionPerformed(ActionEvent ev) { 2734 idv.doMakeControl(new ArrayList(), 2735 (ControlDescriptor)theObject); 2736 } 2737 }); 2738 } 2739 return mi; 2740 } 2741 2742 /* (non-javadoc) 2743 * Overridden so that the toolbar will update upon saving a bundle. 2744 */ 2745 @Override public void displayTemplatesChanged() { 2746 super.displayTemplatesChanged(); 2747 for (JToolBar toolbar : toolbars) { 2748 toolbar.setVisible(false); 2749 populateToolbar(toolbar); 2750 toolbar.setVisible(true); 2751 } 2752 } 2753 2754 /** 2755 * Called when there has been any change to the favorite bundles and is 2756 * most useful for triggering an update to the {@literal "toolbar bundles"}. 2757 */ 2758 @Override public void favoriteBundlesChanged() { 2759 SwingUtilities.invokeLater(() -> { 2760 for (JToolBar toolbar : toolbars) { 2761 toolbar.setVisible(false); 2762 populateToolbar(toolbar); 2763 toolbar.setVisible(true); 2764 } 2765 }); 2766 } 2767 2768 /** 2769 * Show the support request form in a non-swing thread. We do this because we cannot 2770 * call the HttpFormEntry.showUI from a swing thread 2771 * 2772 * @param description Default value for the description form entry 2773 * @param stackTrace The stack trace that caused this error. 2774 * @param dialog The dialog to put the gui in, if non-null. 2775 */ 2776 2777 private void showSupportFormInThread(String description, 2778 String stackTrace, JDialog dialog) { 2779 List<HttpFormEntry> entries = new ArrayList<>(); 2780 2781 StringBuffer extra = new StringBuffer("<h3>McIDAS-V</h3>\n"); 2782 Hashtable<String, String> table = 2783 ((StateManager)getStateManager()).getVersionInfo(); 2784 append(extra, "mcv.version.general", table.get("mcv.version.general")); 2785 append(extra, "mcv.version.build", table.get("mcv.version.build")); 2786 append(extra, "idv.version.general", table.get("idv.version.general")); 2787 append(extra, "idv.version.build", table.get("idv.version.build")); 2788 2789 extra.append("<h3>OS</h3>\n"); 2790 append(extra, "os.name", System.getProperty("os.name")); 2791 append(extra, "os.arch", System.getProperty("os.arch")); 2792 append(extra, "os.version", System.getProperty("os.version")); 2793 2794 extra.append("<h3>Java</h3>\n"); 2795 append(extra, "java.vendor", System.getProperty("java.vendor")); 2796 append(extra, "java.version", System.getProperty("java.version")); 2797 append(extra, "java.home", System.getProperty("java.home")); 2798 2799 StringBuffer javaInfo = new StringBuffer(); 2800 javaInfo.append("Java: home: " + System.getProperty("java.home")); 2801 javaInfo.append(" version: " + System.getProperty("java.version")); 2802 2803 Class c = null; 2804 try { 2805 c = Class.forName("javax.media.j3d.VirtualUniverse"); 2806 Method method = Misc.findMethod(c, "getProperties", 2807 new Class[] {}); 2808 if (method == null) { 2809 javaInfo.append("j3d <1.3"); 2810 } else { 2811 try { 2812 Map m = (Map)method.invoke(c, new Object[] {}); 2813 javaInfo.append(" j3d:" + m.get("j3d.version")); 2814 append(extra, "j3d.version", m.get("j3d.version")); 2815 append(extra, "j3d.vendor", m.get("j3d.vendor")); 2816 append(extra, "j3d.renderer", m.get("j3d.renderer")); 2817 } catch (Exception exc) { 2818 javaInfo.append(" j3d:" + "unknown"); 2819 } 2820 } 2821 } catch (ClassNotFoundException exc) { 2822 append(extra, "j3d", "none"); 2823 } 2824 2825 boolean persistCC = getStore().get("mcv.supportreq.cc", true); 2826 2827 JCheckBox ccMyself = new JCheckBox("Send Copy of Support Request to Me", persistCC); 2828 ccMyself.addActionListener(e -> { 2829 JCheckBox cb = (JCheckBox)e.getSource(); 2830 getStore().put("mcv.supportreq.cc", cb.isSelected()); 2831 }); 2832 2833 boolean doWrap = idv.getProperty(PROP_WRAP_SUPPORT_DESC, true); 2834 2835 HttpFormEntry descriptionEntry; 2836 HttpFormEntry nameEntry; 2837 HttpFormEntry emailEntry; 2838 HttpFormEntry orgEntry; 2839 2840 entries.add(nameEntry = new HttpFormEntry(HttpFormEntry.TYPE_INPUT, 2841 "form_data[fromName]", "Name:", 2842 getStore().get(PROP_HELP_NAME, (String) null))); 2843 entries.add(emailEntry = new HttpFormEntry(HttpFormEntry.TYPE_INPUT, 2844 "form_data[email]", "Your Email:", 2845 getStore().get(PROP_HELP_EMAIL, (String) null))); 2846 entries.add(orgEntry = new HttpFormEntry(HttpFormEntry.TYPE_INPUT, 2847 "form_data[organization]", "Organization:", 2848 getStore().get(PROP_HELP_ORG, (String) null))); 2849 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_INPUT, 2850 "form_data[subject]", "Subject:")); 2851 2852 entries.add( 2853 new HttpFormEntry( 2854 HttpFormEntry.TYPE_LABEL, "", 2855 "<html>Please provide a <i>thorough</i> description of the problem you encountered:</html>")); 2856 entries.add(descriptionEntry = 2857 new FormEntry(doWrap, HttpFormEntry.TYPE_AREA, 2858 "form_data[description]", "Description:", 2859 description, 5, 30, true)); 2860 2861 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_FILE, 2862 "form_data[att_two]", "Attachment 1:", "", 2863 false)); 2864 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_FILE, 2865 "form_data[att_three]", "Attachment 2:", "", 2866 false)); 2867 2868 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_HIDDEN, 2869 "form_data[submit]", "", "Send Email")); 2870 2871 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_HIDDEN, 2872 "form_data[p_version]", "", 2873 getStateManager().getVersion() 2874 + " build date:" 2875 + getStateManager().getBuildDate())); 2876 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_HIDDEN, 2877 "form_data[opsys]", "", 2878 System.getProperty("os.name"))); 2879 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_HIDDEN, 2880 "form_data[hardware]", "", 2881 javaInfo.toString())); 2882 2883 JLabel topLabel = 2884 new JLabel("<html>This form allows you to send a support request to the McIDAS Help Desk.<br></html>"); 2885 2886 JCheckBox includeBundleCbx = 2887 new JCheckBox("Include Current State as Bundle", false); 2888 2889 List<JCheckBox> checkboxes = list(includeBundleCbx, ccMyself); 2890 2891 boolean alreadyHaveDialog = true; 2892 if (dialog == null) { 2893 // NOTE: if the dialog is modeless you can leave alreadyHaveDialog 2894 // alone. If the dialog is modal you need to set alreadyHaveDialog 2895 // to false. 2896 // If alreadyHaveDialog is false with a modeless dialog, the later 2897 // call to HttpFormEntry.showUI will return false and break out of 2898 // the while loop without talking to the HTTP server. 2899 dialog = GuiUtils.createDialog(LogUtil.getCurrentWindow(), 2900 "Support Request Form", false); 2901// alreadyHaveDialog = false; 2902 } 2903 2904 JLabel statusLabel = GuiUtils.cLabel(" "); 2905 JComponent bottom = LayoutUtil.vbox(LayoutUtil.leftVbox(checkboxes), statusLabel); 2906 2907 while (true) { 2908 //Show form. Check if user pressed cancel. 2909 statusLabel.setText(" "); 2910 if ( !HttpFormEntry.showUI(entries, LayoutUtil.inset(topLabel, 10), 2911 bottom, dialog, alreadyHaveDialog)) { 2912 break; 2913 } 2914 statusLabel.setText("Posting support request..."); 2915 2916 //Save persistent state 2917 getStore().put(PROP_HELP_NAME, nameEntry.getValue()); 2918 getStore().put(PROP_HELP_ORG, orgEntry.getValue()); 2919 getStore().put(PROP_HELP_EMAIL, emailEntry.getValue()); 2920 getStore().save(); 2921 2922 List<HttpFormEntry> entriesToPost = 2923 new ArrayList<>(entries); 2924 2925 if ((stackTrace != null) && (stackTrace.length() > 0)) { 2926 entriesToPost.remove(descriptionEntry); 2927 String newDescription = 2928 descriptionEntry.getValue() 2929 + "\n\n******************\nStack trace:\n" + stackTrace; 2930 entriesToPost.add( 2931 new HttpFormEntry( 2932 HttpFormEntry.TYPE_HIDDEN, "form_data[description]", 2933 "Description:", newDescription, 5, 30, true)); 2934 } 2935 2936 try { 2937 extra.append(idv.getPluginManager().getPluginHtml()); 2938 extra.append(getResourceManager().getHtmlView()); 2939 2940 entriesToPost.add(new HttpFormEntry("form_data[att_extra]", 2941 "extra.html", extra.toString().getBytes())); 2942 2943 if (includeBundleCbx.isSelected()) { 2944 entriesToPost.add( 2945 new HttpFormEntry( 2946 "form_data[att_state]", "bundle" + Constants.SUFFIX_MCV, 2947 idv.getPersistenceManager().getBundleXml( 2948 true).getBytes())); 2949 } 2950 entriesToPost.add(new HttpFormEntry(HttpFormEntry.TYPE_HIDDEN, 2951 "form_data[cc_user]", "", 2952 Boolean.toString(getStore().get("mcv.supportreq.cc", true)))); 2953 2954 String[] results = 2955 HttpFormEntry.doPost(entriesToPost, SUPPORT_REQ_URL); 2956 2957 if (results[0] != null) { 2958 GuiUtils.showHtmlDialog( 2959 results[0], "Support Request Response - Error", 2960 "Support Request Response - Error", null, true); 2961 continue; 2962 } 2963 String html = results[1]; 2964 if (html.toLowerCase().indexOf("your email has been sent") 2965 >= 0) { 2966 LogUtil.userMessage("Your support request has been sent"); 2967 break; 2968 } else if (html.toLowerCase().indexOf("required fields") 2969 >= 0) { 2970 LogUtil.userErrorMessage( 2971 "<html>There was a problem submitting your request. <br>Is your email correct?</html>"); 2972 } else { 2973 GuiUtils.showHtmlDialog( 2974 html, "Unknown Support Request Response", 2975 "Unknown Support Request Response", null, true); 2976 System.err.println(html.toLowerCase()); 2977 } 2978 } catch (Exception exc) { 2979 LogUtil.logException("Doing support request form", exc); 2980 } 2981 } 2982 dialog.dispose(); 2983 } 2984 2985 @Override protected IdvXmlUi doMakeIdvXmlUi(IdvWindow window, 2986 List viewManagers, Element skinRoot) 2987 { 2988 return new McIDASVXmlUi(window, viewManagers, idv, skinRoot); 2989 } 2990 2991 /** 2992 * DeInitialize the given menu before it is shown 2993 * @see ucar.unidata.idv.ui.IdvUIManager#historyMenuSelected(JMenu) 2994 */ 2995 @Override 2996 protected void handleMenuDeSelected(final String id, final JMenu menu, final IdvWindow idvWindow) { 2997 super.handleMenuDeSelected(id, menu, idvWindow); 2998 } 2999 3000 /** 3001 * Initialize the given menu before it is shown 3002 * @see ucar.unidata.idv.ui.IdvUIManager#historyMenuSelected(JMenu) 3003 */ 3004 @Override 3005 protected void handleMenuSelected(final String id, final JMenu menu, final IdvWindow idvWindow) { 3006 if (id.equals(MENU_NEWVIEWS)) { 3007 ViewManager last = getVMManager().getLastActiveViewManager(); 3008 menu.removeAll(); 3009 makeViewStateMenu(menu, last); 3010 } else if (id.equals("bundles")) { 3011 menu.removeAll(); 3012 makeBundleMenu(menu); 3013 } else if (id.equals(MENU_NEWDISPLAY_TAB)) { 3014 menu.removeAll(); 3015 doMakeNewDisplayMenu(menu, false); 3016 } else if (id.equals(MENU_NEWDISPLAY)) { 3017 menu.removeAll(); 3018 doMakeNewDisplayMenu(menu, true); 3019 } else if (id.equals("menu.tools.projections.deletesaved")) { 3020 menu.removeAll(); 3021 makeDeleteViewsMenu(menu); 3022 } else if (id.equals("file.default.layout")) { 3023 makeDefaultLayoutMenu(menu); 3024 } else if (id.equals("tools.formulas")) { 3025 menu.removeAll(); 3026 makeFormulasMenu(menu); 3027 } else { 3028 super.handleMenuSelected(id, menu, idvWindow); 3029 } 3030 } 3031 3032 /** A cache of the operand name to value for the user choices */ 3033 private Map operandCache; 3034 3035 @Override public List selectUserChoices(String msg, List userOperands) { 3036 if (operandCache == null) { 3037 operandCache = 3038 (Hashtable) getStore().getEncodedFile("operandcache.xml"); 3039 if (operandCache == null) { 3040 operandCache = new Hashtable(); 3041 } 3042 } 3043 List fields = new ArrayList(); 3044 List components = new ArrayList(); 3045 List persistentCbxs = new ArrayList(); 3046 components.add(new JLabel("Property")); 3047 components.add(new JLabel("Value")); 3048 components.add(new JLabel("Save in Bundle")); 3049 for (int i = 0; i < userOperands.size(); i++) { 3050 DataOperand operand = (DataOperand)userOperands.get(i); 3051 String fieldType = operand.getProperty("type"); 3052 if (fieldType == null) { 3053 fieldType = FIELDTYPE_TEXT; 3054 } 3055 DerivedDataChoice formula = operand.getDataChoice(); 3056 String description = operand.getDescription(); 3057 if (formula != null) { 3058 description = formula.toString(); 3059 } 3060 3061 String label = operand.getLabel(); 3062 Object dflt = operand.getUserDefault(); 3063 Object cacheKeyNewStyle = Misc.newList(description, label, fieldType); 3064 Object cacheKey = Misc.newList(label, fieldType); 3065 3066 Object cachedOperand = null; 3067 boolean oldStyle = operandCache.containsKey(cacheKey); 3068 boolean newStyle = operandCache.containsKey(cacheKeyNewStyle); 3069 3070 // if new style, always use that and ignore old style 3071 // if no new style, proceed as before. 3072 if (newStyle) { 3073 cachedOperand = operandCache.get(cacheKeyNewStyle); 3074 } else if (oldStyle) { 3075 cachedOperand = operandCache.get(cacheKey); 3076 } 3077 3078 if (cachedOperand != null) { 3079 dflt = cachedOperand; 3080 } 3081 3082 JCheckBox cbx = new JCheckBox("", operand.isPersistent()); 3083 persistentCbxs.add(cbx); 3084 JComponent field = null; 3085 JComponent fieldComp = null; 3086 if (fieldType.equals(FIELDTYPE_TEXT)) { 3087 String rowString = operand.getProperty("rows"); 3088 if (rowString == null) { 3089 rowString = "1"; 3090 } 3091 int rows = new Integer(rowString).intValue(); 3092 if (rows == 1) { 3093 field = new JTextField((dflt != null) 3094 ? dflt.toString() 3095 : "", 15); 3096 } else { 3097 field = new JTextArea((dflt != null) 3098 ? dflt.toString() 3099 : "", rows, 15); 3100 fieldComp = GuiUtils.makeScrollPane(field, 200, 100); 3101 } 3102 } else if (fieldType.equals(FIELDTYPE_BOOLEAN)) { 3103 field = new JCheckBox("", ((dflt != null) 3104 ? new Boolean( 3105 dflt.toString()).booleanValue() 3106 : true)); 3107 } else if (fieldType.equals(FIELDTYPE_CHOICE)) { 3108 String choices = operand.getProperty("choices"); 3109 if (choices == null) { 3110 throw new IllegalArgumentException( 3111 "No 'choices' attribute defined for operand: " 3112 + operand); 3113 } 3114 List l = StringUtil.split(choices, ";", true, true); 3115 field = new JComboBox(new Vector(l)); 3116 if ((dflt != null) && l.contains(dflt)) { 3117 ((JComboBox) field).setSelectedItem(dflt); 3118 } 3119 } else if (fieldType.equals(FIELDTYPE_FILE)) { 3120 JTextField fileFld = new JTextField(((dflt != null) 3121 ? dflt.toString() 3122 : ""), 30); 3123 field = fileFld; 3124 String patterns = operand.getProperty("filepattern"); 3125 List filters = null; 3126 if (patterns != null) { 3127 filters = new ArrayList(); 3128 List toks = StringUtil.split(patterns, ";", true, true); 3129 for (int tokIdx = 0; tokIdx < toks.size(); tokIdx++) { 3130 String tok = (String) toks.get(tokIdx); 3131 List subToks = StringUtil.split(tok, ":", true, true); 3132 if (subToks.size() == 2) { 3133 filters.add( 3134 new PatternFileFilter( 3135 (String)subToks.get(0), 3136 (String)subToks.get(1))); 3137 } else { 3138 filters.add(new PatternFileFilter(tok, tok)); 3139 } 3140 } 3141 } 3142 fieldComp = GuiUtils.centerRight(GuiUtils.hfill(fileFld), 3143 GuiUtils.makeFileBrowseButton(fileFld, filters)); 3144 } else if (fieldType.equals(FIELDTYPE_LOCATION)) { 3145 List l = ((dflt != null) 3146 ? StringUtil.split(dflt.toString(), ";", true, true) 3147 : (List) new ArrayList()); 3148 final LatLonWidget llw = new LatLonWidget(); 3149 field = llw; 3150 if (l.size() == 2) { 3151 llw.setLat(Misc.decodeLatLon(l.get(0).toString())); 3152 llw.setLon(Misc.decodeLatLon(l.get(1).toString())); 3153 } 3154 final JButton centerPopupBtn = 3155 GuiUtils.getImageButton("/auxdata/ui/icons/Map16.gif", 3156 getClass()); 3157 centerPopupBtn.setToolTipText("Center on current displays"); 3158 centerPopupBtn.addActionListener(ae -> popupCenterMenu(centerPopupBtn, llw)); 3159 JComponent centerPopup = GuiUtils.inset(centerPopupBtn, 3160 new Insets(0, 0, 0, 4)); 3161 fieldComp = GuiUtils.hbox(llw, centerPopup); 3162 } else if (fieldType.equals(FIELDTYPE_AREA)) { 3163 //TODO: 3164 } else { 3165 throw new IllegalArgumentException("Unknown type: " 3166 + fieldType + " for operand: " + operand); 3167 } 3168 3169 fields.add(field); 3170 label = StringUtil.replace(label, "_", " "); 3171 components.add(GuiUtils.rLabel(label)); 3172 components.add((fieldComp != null) 3173 ? fieldComp 3174 : field); 3175 components.add(cbx); 3176 } 3177 // GuiUtils.tmpColFills = new int[] { GridBagConstraints.HORIZONTAL, 3178 // GridBagConstraints.NONE, 3179 // GridBagConstraints.NONE }; 3180 GuiUtils.tmpInsets = GuiUtils.INSETS_5; 3181 Component contents = GuiUtils.topCenter(new JLabel(msg), 3182 GuiUtils.doLayout(components, 3, 3183 GuiUtils.WT_NYN, GuiUtils.WT_N)); 3184 if ( !GuiUtils.showOkCancelDialog(null, "Select input", contents, 3185 null, fields)) { 3186 return null; 3187 } 3188 List values = new ArrayList(); 3189 for (int i = 0; i < userOperands.size(); i++) { 3190 DataOperand operand = (DataOperand) userOperands.get(i); 3191 String description = operand.getDescription(); 3192 DerivedDataChoice formula = operand.getDataChoice(); 3193 String label = operand.getLabel(); 3194 Object field = fields.get(i); 3195 Object value = null; 3196 Object cacheValue = null; 3197 3198 if (formula != null) { 3199 description = formula.toString(); 3200 } 3201 3202 if (field instanceof JTextComponent) { 3203 value = ((JTextComponent) field).getText().trim(); 3204 } else if (field instanceof JCheckBox) { 3205 value = new Boolean(((JCheckBox)field).isSelected()); 3206 } else if (field instanceof JComboBox) { 3207 value = ((JComboBox) field).getSelectedItem(); 3208 } else if (field instanceof LatLonWidget) { 3209 LatLonWidget llw = (LatLonWidget) field; 3210 value = new LatLonPointImpl(llw.getLat(), llw.getLon()); 3211 cacheValue = llw.getLat() + ";" + llw.getLon(); 3212 } else { 3213 throw new IllegalArgumentException("Unknown field type:" 3214 + field.getClass().getName()); 3215 } 3216 if (cacheValue == null) { 3217 cacheValue = value; 3218 } 3219 JCheckBox cbx = (JCheckBox)persistentCbxs.get(i); 3220 String fieldType = operand.getProperty("type"); 3221 if (fieldType == null) { 3222 fieldType = "text"; 3223 } 3224 3225 Object cacheKey = Misc.newList(description, label, fieldType); 3226 operandCache.put(cacheKey, cacheValue); 3227 values.add(new UserOperandValue(value, cbx.isSelected())); 3228 } 3229 getStore().putEncodedFile("operandcache.xml", operandCache); 3230 return values; 3231 } 3232 3233 private boolean didTabs = false; 3234 private boolean didNewWindow = false; 3235 3236 public void makeDefaultLayoutMenu(final JMenu menu) { 3237 if (menu == null) 3238 throw new NullPointerException("Must provide a non-null default layout menu"); 3239 3240 menu.removeAll(); 3241 JMenuItem saveLayout = new JMenuItem("Save"); 3242 McVGuiUtils.setMenuImage(saveLayout, Constants.ICON_DEFAULTLAYOUTADD_SMALL); 3243 saveLayout.setToolTipText("Save as default layout"); 3244 saveLayout.addActionListener(e -> ((McIDASV)idv).doSaveAsDefaultLayout()); 3245 3246 JMenuItem removeLayout = new JMenuItem("Remove"); 3247 McVGuiUtils.setMenuImage(removeLayout, Constants.ICON_DEFAULTLAYOUTDELETE_SMALL); 3248 removeLayout.setToolTipText("Remove saved default layout"); 3249 removeLayout.addActionListener(e -> idv.doClearDefaults()); 3250 3251 removeLayout.setEnabled(((McIDASV)idv).hasDefaultLayout()); 3252 3253 menu.add(saveLayout); 3254 menu.add(removeLayout); 3255 } 3256 3257 /** 3258 * Bundles any compatible {@link ViewManager} states into 3259 * {@link JMenuItem JMenuItem} and adds said menu items to {@code menu}. 3260 * Incompatible states are ignored. 3261 * 3262 * <p>Each {@code JMenuItem} (except those under the {@literal "Delete"} 3263 * menu--apologies) associates a {@literal "view state"} and an 3264 * {@link ObjectListener}. The {@code ObjectListener} uses this associated 3265 * view state to attempt reinitialization of {@code vm}. 3266 * 3267 * <p>Override reasoning: 3268 * <ul> 3269 * <li> 3270 * terminology ({@literal "views"} rather than {@literal "viewpoints"}). 3271 * </li> 3272 * <li> 3273 * use of {@link #filterVMMStatesWithVM(ViewManager, Collection)} to 3274 * properly detect the {@literal "no saved views"} case. 3275 * </li> 3276 * </ul> 3277 * 3278 * @param menu Menu to populate. Should not be {@code null}. 3279 * @param vm {@code ViewManager} that might get reinitialized. 3280 * Should not be {@code null}. 3281 * 3282 * @see ViewManager#initWith(ViewManager, boolean) 3283 * @see ViewManager#initWith(ViewState) 3284 */ 3285 @Override public void makeViewStateMenu(final JMenu menu, final ViewManager vm) { 3286 List<TwoFacedObject> vmStates = 3287 filterVMMStatesWithVM(vm, getVMManager().getVMState()); 3288 if (vmStates.isEmpty()) { 3289 JMenuItem item = new JMenuItem(Msg.msg("No Saved Views")); 3290 item.setEnabled(false); 3291 menu.add(item); 3292 } else { 3293 JMenu deleteMenu = new JMenu("Delete"); 3294 makeDeleteViewsMenu(deleteMenu); 3295 menu.add(deleteMenu); 3296 } 3297 3298 for (TwoFacedObject tfo : vmStates) { 3299 JMenuItem mi = new JMenuItem(tfo.getLabel().toString()); 3300 menu.add(mi); 3301 mi.addActionListener(new ObjectListener(tfo.getId()) { 3302 public void actionPerformed(final ActionEvent e) { 3303 if (vm == null) { 3304 return; 3305 } 3306 3307 if (theObject instanceof ViewManager) { 3308 vm.initWith((ViewManager)theObject, true); 3309 } else if (theObject instanceof ViewState) { 3310 try { 3311 vm.initWith((ViewState)theObject); 3312 } catch (Throwable ex) { 3313 logException("Initializing view with ViewState", ex); 3314 } 3315 } else { 3316 LogUtil.consoleMessage("UIManager.makeViewStateMenu: Object of unknown type: "+theObject.getClass().getName()); 3317 } 3318 } 3319 }); 3320 } 3321 3322 // the "3" ensures that the "save viewpoint" menu item, the separator, 3323 // and the "delete" menu item are fixed at the top. 3324 new MenuScroller(menu, menu, 125, 3); 3325 } 3326 3327 /** 3328 * Overridden by McIDAS-V to add menu scrolling functionality to the 3329 * {@literal "delete"} submenu. 3330 * 3331 * @param menu {@literal "Delete"} submenu. 3332 */ 3333 @Override public void makeDeleteViewsMenu(JMenu menu) { 3334 super.makeDeleteViewsMenu(menu); 3335 new MenuScroller(menu, menu, 125); 3336 } 3337 3338 /** 3339 * Returns a list of {@link TwoFacedObject}s that are known to be 3340 * compatible with {@code vm}. 3341 * 3342 * <p>This method is currently capable of dealing with 3343 * {@link TwoFacedObject TwoFacedObjects} and 3344 * {@link ViewState ViewStates} within {@code states}. Any other types are 3345 * ignored. 3346 * 3347 * @param vm {@link ViewManager} to use for compatibility tests. 3348 * {@code null} is allowed. 3349 * @param states Collection of objects to test against {@code vm}. 3350 * {@code null} is allowed. 3351 * 3352 * @return Either a {@link List} of compatible {@literal "view states"} 3353 * or an empty {@code List}. 3354 * 3355 * @see ViewManager#isCompatibleWith(ViewManager) 3356 * @see ViewManager#isCompatibleWith(ViewState) 3357 * @see #makeViewStateMenu(JMenu, ViewManager) 3358 */ 3359 public static List<TwoFacedObject> filterVMMStatesWithVM(final ViewManager vm, final Collection<?> states) { 3360 if ((vm == null) || (states == null) || states.isEmpty()) { 3361 return Collections.emptyList(); 3362 } 3363 3364 List<TwoFacedObject> validStates = new ArrayList<>(states.size()); 3365 for (Object obj : states) { 3366 TwoFacedObject tfo = null; 3367 if (obj instanceof TwoFacedObject) { 3368 tfo = (TwoFacedObject)obj; 3369 if (vm.isCompatibleWith((ViewManager)tfo.getId())) { 3370 continue; 3371 } 3372 } else if (obj instanceof ViewState) { 3373 if (!vm.isCompatibleWith((ViewState)obj)) { 3374 continue; 3375 } 3376 tfo = new TwoFacedObject(((ViewState)obj).getName(), obj); 3377 } else { 3378 LogUtil.consoleMessage("UIManager.filterVMMStatesWithVM: Object of unknown type: "+obj.getClass().getName()); 3379 continue; 3380 } 3381 validStates.add(tfo); 3382 } 3383 return validStates; 3384 } 3385 3386 /** 3387 * Overridden to build a custom Display menu. 3388 * @see ucar.unidata.idv.ui.IdvUIManager#initializeDisplayMenu(JMenu) 3389 */ 3390 @Override protected void initializeDisplayMenu(JMenu displayMenu) { 3391 JMenu m; 3392 JMenuItem mi; 3393 3394 // Get the list of possible standalone control descriptors 3395 Hashtable controlsHash = new Hashtable(); 3396 List controlDescriptors = getStandAloneControlDescriptors(); 3397 for (int i = 0; i < controlDescriptors.size(); i++) { 3398 ControlDescriptor cd = (ControlDescriptor)controlDescriptors.get(i); 3399 String cdLabel = cd.getLabel(); 3400 if (cdLabel.equals("Range Rings")) { 3401 controlsHash.put(cdLabel, cd); 3402 } else if (cdLabel.equals("Range and Bearing")) { 3403 controlsHash.put(cdLabel, cd); 3404 } else if (cdLabel.equals("Location Indicator")) { 3405 controlsHash.put(cdLabel, cd); 3406 } else if (cdLabel.equals("Drawing Control")) { 3407 controlsHash.put(cdLabel, cd); 3408 } else if (cdLabel.equals("Transect Drawing Control")) { 3409 controlsHash.put(cdLabel, cd); 3410 } 3411 } 3412 3413 // Build the menu 3414 ControlDescriptor cd; 3415 3416 mi = new JMenuItem("Create Layer from Data Source..."); 3417 mi.addActionListener(ae -> showDashboard("Data Sources")); 3418 displayMenu.add(mi); 3419 3420 mi = new JMenuItem("Layer Controls..."); 3421 mi.addActionListener(ae -> showDashboard("Layer Controls")); 3422 displayMenu.add(mi); 3423 3424 displayMenu.addSeparator(); 3425 3426 cd = (ControlDescriptor)controlsHash.get("Range Rings"); 3427 mi = makeControlDescriptorItem(cd); 3428 mi.setText("Add Range Rings"); 3429 displayMenu.add(mi); 3430 3431 cd = (ControlDescriptor)controlsHash.get("Range and Bearing"); 3432 mi = makeControlDescriptorItem(cd); 3433 McVGuiUtils.setMenuImage(mi, Constants.ICON_RANGEANDBEARING_SMALL); 3434 mi.setText("Add Range and Bearing"); 3435 displayMenu.add(mi); 3436 3437 displayMenu.addSeparator(); 3438 3439 cd = (ControlDescriptor)controlsHash.get("Transect Drawing Control"); 3440 mi = makeControlDescriptorItem(cd); 3441 mi.setText("Draw Transect..."); 3442 displayMenu.add(mi); 3443 3444 cd = (ControlDescriptor)controlsHash.get("Drawing Control"); 3445 mi = makeControlDescriptorItem(cd); 3446 mi.setText("Draw Freely..."); 3447 displayMenu.add(mi); 3448 3449 displayMenu.addSeparator(); 3450 3451 cd = (ControlDescriptor)controlsHash.get("Location Indicator"); 3452 mi = makeControlDescriptorItem(cd); 3453 McVGuiUtils.setMenuImage(mi, Constants.ICON_LOCATION_SMALL); 3454 mi.setText("Add Location Indicator"); 3455 displayMenu.add(mi); 3456 3457 ControlDescriptor locationDescriptor = idv.getControlDescriptor("locationcontrol"); 3458 if (locationDescriptor != null) { 3459 List stations = idv.getLocationList(); 3460 ObjectListener listener = new ObjectListener(locationDescriptor) { 3461 public void actionPerformed(ActionEvent ae, Object obj) { 3462 addStationDisplay((NamedStationTable) obj, (ControlDescriptor) theObject); 3463 } 3464 }; 3465 List menuItems = NamedStationTable.makeMenuItems(stations, listener); 3466 displayMenu.add(MenuUtil.makeMenu("Plot Location Labels", menuItems)); 3467 } 3468 3469 displayMenu.addSeparator(); 3470 3471 mi = new JMenuItem("Add Background Image"); 3472 McVGuiUtils.setMenuImage(mi, Constants.ICON_BACKGROUND_SMALL); 3473 mi.addActionListener(ae -> getIdv().doMakeBackgroundImage()); 3474 displayMenu.add(mi); 3475 3476 mi = new JMenuItem("Reset Map Layer to Defaults"); 3477 mi.addActionListener(ae -> { 3478 // TODO: Call IdvUIManager.addDefaultMap()... should be made private 3479// addDefaultMap(); 3480 ControlDescriptor mapDescriptor = idv.getControlDescriptor("mapdisplay"); 3481 if (mapDescriptor == null) { 3482 return; 3483 } 3484 String attrs = "initializeAsDefault=true;displayName=Default Background Maps;"; 3485 idv.doMakeControl(new ArrayList(), mapDescriptor, attrs, null); 3486 }); 3487 displayMenu.add(mi); 3488 Msg.translateTree(displayMenu); 3489 } 3490 3491 /** 3492 * Get the window title from the skin 3493 * 3494 * @param index the skin index 3495 * 3496 * @return the title 3497 */ 3498 private String getWindowTitleFromSkin(final int index) { 3499 if (!skinToTitle.containsKey(index)) { 3500 IdvResourceManager mngr = getResourceManager(); 3501 XmlResourceCollection skins = mngr.getXmlResources(mngr.RSC_SKIN); 3502 List<String> names = StringUtil.split(skins.getShortName(index), ">", true, true); 3503 String title = getStateManager().getTitle(); 3504 if (!names.isEmpty()) { 3505 title = title + " - " + StringUtil.join(" - ", names); 3506 } 3507 skinToTitle.put(index, title); 3508 } 3509 return skinToTitle.get(index); 3510 } 3511 3512 @SuppressWarnings("unchecked") 3513 @Override public Hashtable getMenuIds() { 3514 return menuIds; 3515 } 3516 3517 @SuppressWarnings("unchecked") 3518 @Override public JMenuBar doMakeMenuBar(final IdvWindow idvWindow) { 3519 Hashtable<String, JMenuItem> menuMap = new Hashtable<>(); 3520 JMenuBar menuBar = new JMenuBar(); 3521 final IdvResourceManager mngr = getResourceManager(); 3522 XmlResourceCollection xrc = mngr.getXmlResources(mngr.RSC_MENUBAR); 3523 Hashtable<String, ImageIcon> actionIcons = new Hashtable<>(); 3524 3525 for (int i = 0; i < xrc.size(); i++) { 3526 GuiUtils.processXmlMenuBar(xrc.getRoot(i), menuBar, getIdv(), menuMap, actionIcons); 3527 } 3528 3529 menuIds = new Hashtable<>(menuMap); 3530 3531 // Ensure that the "help" menu is the last menu. 3532 JMenuItem helpMenu = menuMap.get(MENU_HELP); 3533 if (helpMenu != null) { 3534 menuBar.remove(helpMenu); 3535 menuBar.add(helpMenu); 3536 } 3537 3538 //TODO: Perhaps we will put the different skins in the menu? 3539 JMenu newDisplayMenu = (JMenu)menuMap.get(MENU_NEWDISPLAY); 3540 if (newDisplayMenu != null) { 3541 MenuUtil.makeMenu(newDisplayMenu, makeSkinMenuItems(makeMenuBarActionListener(), true, false)); 3542 } 3543 3544// final JMenu publishMenu = menuMap.get(MENU_PUBLISH); 3545// if (publishMenu != null) { 3546// if (!getPublishManager().isPublishingEnabled()) 3547// publishMenu.getParent().remove(publishMenu); 3548// else 3549// getPublishManager().initMenu(publishMenu); 3550// } 3551 3552 for (Entry<String, JMenuItem> e : menuMap.entrySet()) { 3553 if (!(e.getValue() instanceof JMenu)) { 3554 continue; 3555 } 3556 String menuId = e.getKey(); 3557 JMenu menu = (JMenu)e.getValue(); 3558 menu.addMenuListener(makeMenuBarListener(menuId, menu, idvWindow)); 3559 } 3560 return menuBar; 3561 } 3562 3563 private final ActionListener makeMenuBarActionListener() { 3564 final IdvResourceManager mngr = getResourceManager(); 3565 return ae -> { 3566 XmlResourceCollection skins = mngr.getXmlResources(mngr.RSC_SKIN); 3567 int skinIndex = ((Integer)ae.getSource()).intValue(); 3568 createNewWindow(null, true, getWindowTitleFromSkin(skinIndex), 3569 skins.get(skinIndex).toString(), 3570 skins.getRoot(skinIndex, false), true, null); 3571 }; 3572 } 3573 3574 private final MenuListener makeMenuBarListener(final String id, final JMenu menu, final IdvWindow idvWindow) { 3575 return new MenuListener() { 3576 public void menuCanceled(final MenuEvent e) { } 3577 public void menuDeselected(final MenuEvent e) { handleMenuDeSelected(id, menu, idvWindow); } 3578 public void menuSelected(final MenuEvent e) { handleMenuSelected(id, menu, idvWindow); } 3579 }; 3580 } 3581 3582 /** 3583 * Handle mouse clicks that occur within the toolbar. 3584 */ 3585 private class PopupListener extends MouseAdapter { 3586 3587 private JPopupMenu popup; 3588 3589 public PopupListener(JPopupMenu p) { 3590 popup = p; 3591 } 3592 3593 // handle right clicks on os x and linux 3594 public void mousePressed(MouseEvent e) { 3595 if (e.isPopupTrigger()) { 3596 popup.show(e.getComponent(), e.getX(), e.getY()); 3597 } 3598 } 3599 3600 // Windows doesn't seem to trigger mousePressed() for right clicks, but 3601 // never fear; mouseReleased() does the job. 3602 public void mouseReleased(MouseEvent e) { 3603 if (e.isPopupTrigger()) { 3604 popup.show(e.getComponent(), e.getX(), e.getY()); 3605 } 3606 } 3607 } 3608 3609 /** 3610 * Handle (polymorphically) the {@link ucar.unidata.idv.ui.DataControlDialog}. 3611 * This dialog is used to either select a display control to create 3612 * or is used to set the timers used for a {@link ucar.unidata.data.DataSource}. 3613 * 3614 * @param dcd The dialog 3615 */ 3616 public void processDialog(DataControlDialog dcd) { 3617 int estimatedMB = getEstimatedMegabytes(dcd); 3618 if (estimatedMB > 0) { 3619 double totalMem = Runtime.getRuntime().maxMemory(); 3620 double highMem = Runtime.getRuntime().totalMemory(); 3621 double freeMem = Runtime.getRuntime().freeMemory(); 3622 double usedMem = (highMem - freeMem); 3623 int availableMB = Math.round( ((float)totalMem - (float)usedMem) / 1024f / 1024f); 3624 int percentOfAvailable = Math.round((float)estimatedMB / (float)availableMB * 100f); 3625 if (percentOfAvailable > 95) { 3626 String message = "<html>You are attempting to load " + estimatedMB + "MB of data,<br>"; 3627 message += "which exceeds 95% of total amount available (" + availableMB +"MB).<br>"; 3628 message += "Data load cancelled.</html>"; 3629 JComponent msgLabel = new JLabel(message); 3630 GuiUtils.showDialog("Data Size", msgLabel); 3631 return; 3632 } else if (percentOfAvailable >= 75) { 3633 String message = "<html>You are attempting to load " + estimatedMB + "MB of data,<br>"; 3634 message += percentOfAvailable + "% of the total amount available (" + availableMB + "MB).<br>"; 3635 message += "Continue loading data?</html>"; 3636 JComponent msgLabel = new JLabel(message); 3637 if (!GuiUtils.askOkCancel("Data Size", msgLabel)) { 3638 return; 3639 } 3640 } 3641 } 3642 super.processDialog(dcd); 3643 } 3644 3645 /** 3646 * Estimate the number of megabytes that will be used by this data selection 3647 * 3648 * @param dcd Data control dialog containing the data selection whose size 3649 * we'd like to estimate. Cannot be {@code null}. 3650 * 3651 * @return Rough estimate of the size (in megabytes) of the 3652 * {@code DataSelection} within {@code dcd}. 3653 */ 3654 protected int getEstimatedMegabytes(DataControlDialog dcd) { 3655 int estimatedMB = 0; 3656 DataChoice dataChoice = dcd.getDataChoice(); 3657 if (dataChoice != null) { 3658 Object[] selectedControls = dcd.getSelectedControls(); 3659 for (int i = 0; i < selectedControls.length; i++) { 3660 ControlDescriptor cd = (ControlDescriptor) selectedControls[i]; 3661 3662 //Check if the data selection is ok 3663 if(!dcd.getDataSelectionWidget().okToCreateTheDisplay(cd.doesLevels())) { 3664 continue; 3665 } 3666 3667 DataSelection dataSelection = dcd.getDataSelectionWidget().createDataSelection(cd.doesLevels()); 3668 3669 // Get the size in pixels of the requested image 3670 Object gotSize = dataSelection.getProperty("SIZE"); 3671 if (gotSize == null) { 3672 continue; 3673 } 3674 List<String> dims = StringUtil.split(gotSize, " ", false, false); 3675 int myLines = -1; 3676 int myElements = -1; 3677 if (dims.size() == 2) { 3678 try { 3679 myLines = Integer.parseInt(dims.get(0)); 3680 myElements = Integer.parseInt(dims.get(1)); 3681 } 3682 catch (Exception e) { } 3683 } 3684 3685 // Get the count of times requested 3686 int timeCount = 1; 3687 DataSelectionWidget dsw = dcd.getDataSelectionWidget(); 3688 List times = dsw.getSelectedDateTimes(); 3689 List timesAll = dsw.getAllDateTimes(); 3690 if ((times != null) && !times.isEmpty()) { 3691 timeCount = times.size(); 3692 } else if ((timesAll != null) && !timesAll.isEmpty()) { 3693 timeCount = timesAll.size(); 3694 } 3695 3696 // Total number of pixels 3697 // Assumed lines x elements x times x 4bytes 3698 // Empirically seems to be taking *twice* that (64bit fields??) 3699 float totalPixels = (float)myLines * (float)myElements * (float)timeCount; 3700 float totalBytes = totalPixels * 4 * 2; 3701 estimatedMB += Math.round(totalBytes / 1024f / 1024f); 3702 3703 int additionalMB = 0; 3704 // Empirical tests show that textures are not affecting 3705 // required memory... comment out for now 3706 /* 3707 int textureDimensions = 2048; 3708 int mbPerTexture = Math.round((float)textureDimensions * (float)textureDimensions * 4 / 1024f / 1024f); 3709 int textureCount = (int)Math.ceil((float)myLines / 2048f) * (int)Math.ceil((float)myElements / 2048f); 3710 int additionalMB = textureCount * mbPerTexture * timeCount; 3711 */ 3712 estimatedMB += additionalMB; 3713 } 3714 } 3715 return estimatedMB; 3716 } 3717 3718 /** 3719 * Represents a SavedBundle as a tree. 3720 */ 3721 private class BundleTreeNode { 3722 3723 private String name; 3724 3725 private SavedBundle bundle; 3726 3727 private List<BundleTreeNode> kids; 3728 3729 /** 3730 * This constructor is used to build a node that is considered a 3731 * {@literal "parent"}. 3732 * 3733 * These nodes only have child nodes, no {@code SavedBundles}. This 3734 * was done so that distinguishing between bundles and bundle 3735 * subcategories would be easy. 3736 * 3737 * @param name The name of this node. For a parent node with 3738 * {@literal "Toolbar>cat"} as the path, the name parameter would 3739 * contain only {@literal "cat"}. 3740 */ 3741 public BundleTreeNode(String name) { 3742 this(name, null); 3743 } 3744 3745 /** 3746 * Nodes constructed using this constructor can only ever be child 3747 * nodes. 3748 * 3749 * @param name The name of the SavedBundle. 3750 * @param bundle A reference to the SavedBundle. 3751 */ 3752 public BundleTreeNode(String name, SavedBundle bundle) { 3753 this.name = name; 3754 this.bundle = bundle; 3755 kids = new LinkedList<>(); 3756 } 3757 3758 /** 3759 * @param child The node to be added to the current node. 3760 */ 3761 public void addChild(BundleTreeNode child) { 3762 kids.add(child); 3763 } 3764 3765 /** 3766 * @return Returns all child nodes of this node. 3767 */ 3768 public List<BundleTreeNode> getChildren() { 3769 return kids; 3770 } 3771 3772 /** 3773 * @return Return the SavedBundle associated with this node (if any). 3774 */ 3775 public SavedBundle getBundle() { 3776 return bundle; 3777 } 3778 3779 /** 3780 * @return The name of this node. 3781 */ 3782 public String getName() { 3783 return name; 3784 } 3785 } 3786 3787 /** 3788 * A type of {@code HttpFormEntry} that supports line wrapping for 3789 * text area entries. 3790 * 3791 * @see HttpFormEntry 3792 */ 3793 private static class FormEntry extends HttpFormEntry { 3794 /** Initial contents of this entry. */ 3795 private String value = ""; 3796 3797 /** Whether or not the JTextArea should wrap lines. */ 3798 private boolean wrap = true; 3799 3800 /** Entry type. Used to remain compatible with the IDV. */ 3801 private int type = HttpFormEntry.TYPE_AREA; 3802 3803 /** Number of rows in the JTextArea. */ 3804 private int rows = 5; 3805 3806 /** Number of columns in the JTextArea. */ 3807 private int cols = 30; 3808 3809 /** GUI representation of this entry. */ 3810 private JTextArea component = new JTextArea(value, rows, cols); 3811 3812 /** 3813 * Required to keep Java happy. 3814 */ 3815 public FormEntry() { 3816 super(HttpFormEntry.TYPE_AREA, "form_data[description]", 3817 "Description:"); 3818 } 3819 3820 /** 3821 * Using this constructor allows McIDAS-V to control whether or not a 3822 * HttpFormEntry performs line wrapping for JTextArea components. 3823 * 3824 * @param wrap Whether or not line wrapping should be enabled. 3825 * @param type Type of this entry 3826 * @param name Name 3827 * @param label Label 3828 * @param value Initial value 3829 * @param rows Number of rows. 3830 * @param cols Number of columns. 3831 * @param required Whether or not the entry will be required. 3832 */ 3833 public FormEntry(boolean wrap, int type, String name, String label, String value, int rows, int cols, boolean required) { 3834 super(type, name, label, value, rows, cols, required); 3835 this.type = type; 3836 this.rows = rows; 3837 this.cols = cols; 3838 this.wrap = wrap; 3839 } 3840 3841 /** 3842 * Overrides the IDV method so that the McIDAS-V support request form 3843 * will wrap lines in the "Description" field. 3844 * 3845 * @param guiComps List to which this instance should be added. 3846 */ 3847 @SuppressWarnings("unchecked") 3848 @Override public void addToGui(List guiComps) { 3849 if (type == HttpFormEntry.TYPE_AREA) { 3850 Dimension minSize = new Dimension(500, 200); 3851 guiComps.add(LayoutUtil.top(GuiUtils.rLabel(getLabel()))); 3852 component.setLineWrap(wrap); 3853 component.setWrapStyleWord(wrap); 3854 JScrollPane sp = new JScrollPane(component); 3855 sp.setPreferredSize(minSize); 3856 sp.setMinimumSize(minSize); 3857 guiComps.add(sp); 3858 } else { 3859 super.addToGui(guiComps); 3860 } 3861 } 3862 3863 /** 3864 * Since the IDV doesn't provide a getComponent for 3865 * {@code addToGui}, we must make our {@code component} field 3866 * local to this class. 3867 * Hijacks any value requests so that the local {@code component} 3868 * field is queried, not the IDV's. 3869 * 3870 * @return Contents of form. 3871 */ 3872 @Override public String getValue() { 3873 if (type != HttpFormEntry.TYPE_AREA) { 3874 return super.getValue(); 3875 } 3876 return component.getText(); 3877 } 3878 3879 /** 3880 * Hijacks any requests to set the {@code component} field's text. 3881 */ 3882 @Override public void setValue(final String newValue) { 3883 if (type == HttpFormEntry.TYPE_AREA) { 3884 component.setText(newValue); 3885 } else { 3886 super.setValue(newValue); 3887 } 3888 } 3889 } 3890 3891 /** 3892 * A {@code ToolbarStyle} is a representation of the way icons associated 3893 * with current toolbar actions should be displayed. This notion is so far 3894 * limited to the sizing of icons, but that may change. 3895 */ 3896 public enum ToolbarStyle { 3897 /** 3898 * Represents the current toolbar actions as large icons. Currently, 3899 * {@literal "large"} is defined as {@code 32 x 32} pixels. 3900 */ 3901 LARGE("Large Icons", "action.icons.large", 32), 3902 3903 /** 3904 * Represents the current toolbar actions as medium icons. Currently, 3905 * {@literal "medium"} is defined as {@code 22 x 22} pixels. 3906 */ 3907 MEDIUM("Medium Icons", "action.icons.medium", 22), 3908 3909 /** 3910 * Represents the current toolbar actions as small icons. Currently, 3911 * {@literal "small"} is defined as {@code 16 x 16} pixels. 3912 */ 3913 SMALL("Small Icons", "action.icons.small", 16); 3914 3915 /** Label to use in the toolbar customization popup menu. */ 3916 private final String label; 3917 3918 /** Signals that the user selected a specific icon size. */ 3919 private final String action; 3920 3921 /** Icon dimensions. Each icon should be {@code size * size}. */ 3922 private final int size; 3923 3924 /** 3925 * {@link #size} in {@link String} form, merely for use with the IDV's 3926 * preference functionality. 3927 */ 3928 private final String sizeAsString; 3929 3930 /** 3931 * Initializes a toolbar style. 3932 * 3933 * @param label Label used in the toolbar popup menu. 3934 * @param action Command that signals the user selected this toolbar 3935 * style. 3936 * @param size Dimensions of the icons. 3937 * 3938 * @throws NullPointerException if {@code label} or {@code action} are 3939 * null. 3940 * 3941 * @throws IllegalArgumentException if {@code size} is not positive. 3942 */ 3943 ToolbarStyle(final String label, final String action, final int size) { 3944 requireNonNull(label, "Label cannot be null."); 3945 requireNonNull(action, "Action cannot be null."); 3946 3947 if (size <= 0) { 3948 throw new IllegalArgumentException("Size must be a positive integer"); 3949 } 3950 3951 this.label = label; 3952 this.action = action; 3953 this.size = size; 3954 this.sizeAsString = Integer.toString(size); 3955 } 3956 3957 /** 3958 * Returns the label to use as a brief description of this style. 3959 * 3960 * @return Description of style (suitable for a label). 3961 */ 3962 public String getLabel() { 3963 return label; 3964 } 3965 3966 /** 3967 * Returns the action command associated with this style. 3968 * 3969 * @return This style's {@literal "action command"}. 3970 */ 3971 public String getAction() { 3972 return action; 3973 } 3974 3975 /** 3976 * Returns the dimensions of icons used in this style. 3977 * 3978 * @return Dimensions of this style's icons. 3979 */ 3980 public int getSize() { 3981 return size; 3982 } 3983 3984 /** 3985 * Returns {@link #size} as a {@link String} to make cooperating with 3986 * the IDV preferences code easier. 3987 * 3988 * @return String representation of this style's icon dimensions. 3989 */ 3990 public String getSizeAsString() { 3991 return sizeAsString; 3992 } 3993 3994 /** 3995 * Returns a brief description of this {@code ToolbarStyle}. 3996 * 3997 * <p>A typical example: 3998 * {@code [ToolbarStyle@1337: label="Large Icons", size=32]} 3999 * 4000 * <p>Note that the format and details provided are subject to change. 4001 * 4002 * @return String representation of this {@code ToolbarStyle} instance. 4003 */ 4004 public String toString() { 4005 return String.format("[ToolbarStyle@%x: label=%s, size=%d]", 4006 hashCode(), label, size); 4007 } 4008 4009 /** 4010 * Convenience method for build the toolbar customization popup menu. 4011 * 4012 * @param manager {@link UIManager} that will be listening for action 4013 * commands. 4014 * 4015 * @return Menu item that has {@code manager} listening for 4016 * {@link #action}. 4017 */ 4018 protected JMenuItem buildMenuItem(final UIManager manager) { 4019 JMenuItem item = new JRadioButtonMenuItem(label); 4020 item.setActionCommand(action); 4021 item.addActionListener(manager); 4022 return item; 4023 } 4024 } 4025 4026 /** 4027 * Represents what McIDAS-V {@literal "knows"} about IDV actions. 4028 */ 4029 protected enum ActionAttribute { 4030 4031 /** 4032 * Unique identifier for an IDV action. Required attribute. 4033 * 4034 * @see IdvUIManager#ATTR_ID 4035 */ 4036 ID(ATTR_ID), 4037 4038 /** 4039 * Path to an icon for this action. Currently required. Note that 4040 * McIDAS-V differs from the IDV in that actions must support different 4041 * icon sizes. This is implemented in McIDAS-V by simply having the value 4042 * of this path be a valid {@literal "format string"}, 4043 * such as {@code image="/edu/wisc/ssec/mcidasv/resources/icons/toolbar/background-image%d.png"} 4044 * 4045 * <p>The upshot is that this value <b>will not be a valid path in 4046 * McIDAS-V</b>. Use either {@link IdvAction#getMenuIcon()} or 4047 * {@link IdvAction#getIconForStyle}. 4048 * 4049 * @see IdvUIManager#ATTR_IMAGE 4050 * @see IdvAction#getRawIconPath() 4051 * @see IdvAction#getMenuIcon() 4052 * @see IdvAction#getIconForStyle 4053 */ 4054 ICON(ATTR_IMAGE), 4055 4056 /** 4057 * Brief description of a IDV action. Required attribute. 4058 * @see IdvUIManager#ATTR_DESCRIPTION 4059 */ 4060 DESCRIPTION(ATTR_DESCRIPTION), 4061 4062 /** 4063 * Allows actions to be clustered into arbitrary groups. Currently 4064 * optional; defaults to {@literal "General"}. 4065 * @see IdvUIManager#ATTR_GROUP 4066 */ 4067 GROUP(ATTR_GROUP, "General"), 4068 4069 /** 4070 * Actual method call used to invoke a given IDV action. Required 4071 * attribute. 4072 * @see IdvUIManager#ATTR_ACTION 4073 */ 4074 ACTION(ATTR_ACTION); 4075 4076 /** 4077 * A blank {@link String} if this is a required attribute, or a 4078 * {@code String} value to use in case this attribute has not been 4079 * specified by a given IDV action. 4080 */ 4081 private final String defaultValue; 4082 4083 /** 4084 * String representation of this attribute as used by the IDV. 4085 * @see #asIdvString() 4086 */ 4087 private final String idvString; 4088 4089 /** Whether or not this attribute is required. */ 4090 private final boolean required; 4091 4092 /** 4093 * Creates a constant that represents a required IDV action attribute. 4094 * 4095 * @param idvString Corresponding IDV attribute {@link String}. Cannot be {@code null}. 4096 * 4097 * @throws NullPointerException if {@code idvString} is {@code null}. 4098 */ 4099 ActionAttribute(final String idvString) { 4100 requireNonNull(idvString, "Cannot be associated with a null IDV action attribute String"); 4101 4102 this.idvString = idvString; 4103 this.defaultValue = ""; 4104 this.required = true; 4105 } 4106 4107 /** 4108 * Creates a constant that represents an optional IDV action attribute. 4109 * 4110 * @param idvString Corresponding IDV attribute {@link String}. 4111 * Cannot be {@code null}. 4112 * @param defValue Default value for actions that do not have this 4113 * attribute. Cannot be {@code null} or an empty {@code String}. 4114 * 4115 * @throws NullPointerException if either {@code idvString} or 4116 * {@code defValue} is {@code null}. 4117 * @throws IllegalArgumentException if {@code defValue} is an empty 4118 * {@code String}. 4119 * 4120 */ 4121 ActionAttribute(final String idvString, final String defValue) { 4122 requireNonNull(idvString, "Cannot be associated with a null IDV action attribute String"); 4123 Contract.notNull(defValue, "Optional action attribute \"%s\" requires a non-null default value", toString()); 4124 Contract.checkArg(!defValue.equals(""), "Optional action attribute \"%s\" requires something more descriptive than an empty String", toString()); 4125 4126 this.idvString = idvString; 4127 this.defaultValue = defValue; 4128 this.required = (defaultValue.equals("")); 4129 } 4130 4131 /** 4132 * @return The {@link String} representation of this attribute, as is 4133 * used by the IDV. 4134 * 4135 * @see IdvUIManager#ATTR_ACTION 4136 * @see IdvUIManager#ATTR_DESCRIPTION 4137 * @see IdvUIManager#ATTR_GROUP 4138 * @see IdvUIManager#ATTR_ID 4139 * @see IdvUIManager#ATTR_IMAGE 4140 */ 4141 public String asIdvString() { return idvString; } 4142 4143 /** 4144 * @return {@literal "Default value"} for this attribute. 4145 * Blank {@link String}s imply that the attribute is required (and 4146 * thus lacks a true default value). 4147 */ 4148 public String defaultValue() { return defaultValue; } 4149 4150 /** 4151 * @return Whether or not this attribute is a required attribute for 4152 * valid {@link IdvAction}s. 4153 */ 4154 public boolean isRequired() { return required; } 4155 } 4156 4157 /** 4158 * Represents the set of known {@link IdvAction IdvActions} in an idiom 4159 * that can be easily used by both the IDV and McIDAS-V. 4160 */ 4161 // TODO(jon:101): use Sets instead of maps and whatnot 4162 // TODO(jon:103): create an invalid IdvAction 4163 public static final class IdvActions { 4164 4165 /** Maps {@literal "id"} values to {@link IdvAction IdvActions}. */ 4166 private final Map<String, IdvAction> idToAction = 4167 new ConcurrentHashMap<>(); 4168 4169 /** 4170 * Collects {@link IdvAction IdvActions} {@literal "under"} common 4171 * group values. 4172 */ 4173 // TODO(jon:102): this should probably become concurrency-friendly. 4174 private final Map<String, Set<IdvAction>> groupToActions = 4175 new LinkedHashMap<>(); 4176 4177 /** 4178 * Creates an object that represents the application's 4179 * {@link IdvAction IdvActions}. 4180 * 4181 * @param idv Reference to the IDV {@literal "god"} object. 4182 * Cannot be {@code null}. 4183 * @param collectionId IDV resource collection that contains our 4184 * actions. Cannot be {@code null}. 4185 * 4186 * @throws NullPointerException if {@code idv} or {@code collectionId} 4187 * is {@code null}. 4188 */ 4189 public IdvActions(final IntegratedDataViewer idv, final XmlIdvResource collectionId) { 4190 requireNonNull(idv, "Cannot provide a null IDV reference"); 4191 requireNonNull(collectionId, "Cannot build actions from a null collection id"); 4192 4193 // TODO(jon): benchmark use of xpath 4194 String query = "//action[@id and @image and @description and @action]"; 4195 for (Element e : elements(idv, collectionId, query)) { 4196 IdvAction a = new IdvAction(e); 4197 String id = a.getAttribute(ActionAttribute.ID); 4198 idToAction.put(id, a); 4199 String group = a.getAttribute(ActionAttribute.GROUP); 4200 if (!groupToActions.containsKey(group)) { 4201 groupToActions.put(group, new LinkedHashSet<>()); 4202 } 4203 Set<IdvAction> groupedIds = groupToActions.get(group); 4204 groupedIds.add(a); 4205 } 4206 } 4207 4208 /** 4209 * Attempts to return the {@link IdvAction} associated with the given 4210 * {@code actionId}. 4211 * 4212 * @param actionId Identifier to use in the search. Cannot be 4213 * {@code null}. 4214 * 4215 * @return Either the {@code IdvAction} that matches {@code actionId} 4216 * or {@code null} if there was no match. 4217 * 4218 * @throws NullPointerException if {@code actionId} is {@code null}. 4219 */ 4220 // TODO(jon:103) here 4221 public IdvAction getAction(final String actionId) { 4222 requireNonNull(actionId, "Null action identifiers are not allowed"); 4223 return idToAction.get(actionId); 4224 } 4225 4226 /** 4227 * Searches for the action associated with {@code actionId} and 4228 * returns the value associated with the given {@link ActionAttribute}. 4229 * 4230 * @param actionId Identifier to search for. Cannot be {@code null}. 4231 * @param attr Attribute whose value is desired. Cannot be {@code null}. 4232 * 4233 * @return Either the desired attribute value of the desired action, 4234 * or {@code null} if {@code actionId} has no associated action. 4235 * 4236 * @throws NullPointerException if either {@code actionId} or 4237 * {@code attr} is {@code null}. 4238 */ 4239 // TODO(jon:103) here 4240 public String getAttributeForAction(final String actionId, final ActionAttribute attr) { 4241 requireNonNull(actionId, "Null action identifiers are not allowed"); 4242 requireNonNull(attr, "Actions cannot have values associated with a null attribute"); 4243 IdvAction action = idToAction.get(actionId); 4244 if (action == null) { 4245 return null; 4246 } 4247 return action.getAttribute(attr); 4248 } 4249 4250 /** 4251 * Attempts to return the XML {@link Element} that 4252 * {@literal "represents"} the action associated with {@code actionId}. 4253 * 4254 * @param actionId Identifier whose XML element is desired. 4255 * Cannot be {@code null}. 4256 * 4257 * @return Either the XML element associated with {@code actionId} or 4258 * {@code null}. 4259 * 4260 * @throws NullPointerException if {@code actionId} is {@code null}. 4261 * 4262 * @see IdvAction#originalElement 4263 */ 4264 // TODO(jon:103) here 4265 public Element getElementForAction(final String actionId) { 4266 requireNonNull(actionId, "Cannot search for a null action identifier"); 4267 IdvAction action = idToAction.get(actionId); 4268 if (action == null) { 4269 return null; 4270 } 4271 return action.getElement(); 4272 } 4273 4274 /** 4275 * Attempts to return an {@link Icon} for a given {@link ActionAttribute#ID} and 4276 * {@link ToolbarStyle}. 4277 * 4278 * @param actionId ID of the action whose {@literal "styled"} icon is 4279 * desired. Cannot be {@code null}. 4280 * @param style Desired {@code Icon} style. Cannot be {@code null}. 4281 * 4282 * @return Either the {@code Icon} associated with {@code actionId} 4283 * and {@code style}, or {@code null}. 4284 * 4285 * @throws NullPointerException if either {@code actionId} or 4286 * {@code style} is {@code null}. 4287 */ 4288 // TODO(jon:103) here 4289 public Icon getStyledIconFor(final String actionId, final ToolbarStyle style) { 4290 requireNonNull(actionId, "Cannot get an icon for a null action identifier"); 4291 requireNonNull(style, "Cannot get an icon for a null ToolbarStyle"); 4292 IdvAction a = idToAction.get(actionId); 4293 if (a == null) { 4294 return null; 4295 } 4296 return a.getIconForStyle(style); 4297 } 4298 4299 // TODO(jon:105): replace with something better 4300 public List<String> getAttributes(final ActionAttribute attr) { 4301 requireNonNull(attr, "Actions cannot have null attributes"); 4302 List<String> attributeList = arrList(idToAction.size()); 4303 for (Map.Entry<String, IdvAction> entry : idToAction.entrySet()) { 4304 attributeList.add(entry.getValue().getAttribute(attr)); 4305 } 4306 return attributeList; 4307 } 4308 4309 /** 4310 * @return List of all known {@code IdvAction}s. 4311 */ 4312 public List<IdvAction> getAllActions() { 4313 return arrList(idToAction.values()); 4314 } 4315 4316 /** 4317 * @return List of all known action groupings. 4318 * 4319 * @see ActionAttribute#GROUP 4320 * @see #getActionsForGroup(String) 4321 */ 4322 public List<String> getAllGroups() { 4323 return arrList(groupToActions.keySet()); 4324 } 4325 4326 /** 4327 * Returns the {@link Set} of {@link IdvAction}s associated with the 4328 * given {@code group}. 4329 * 4330 * @param group Group whose associated actions you want. Cannot be 4331 * {@code null}. 4332 * 4333 * @return Collection of {@code IdvAction}s associated with 4334 * {@code group}. A blank collection is returned if there are no actions 4335 * associated with {@code group}. 4336 * 4337 * @throws NullPointerException if {@code group} is {@code null}. 4338 * 4339 * @see ActionAttribute#GROUP 4340 * @see #getAllGroups() 4341 */ 4342 public Set<IdvAction> getActionsForGroup(final String group) { 4343 requireNonNull(group, "Actions cannot be associated with a null group"); 4344 if (!groupToActions.containsKey(group)) { 4345 return Collections.emptySet(); 4346 } 4347 return groupToActions.get(group); 4348 } 4349 4350 /** 4351 * Returns a summary of the known IDV actions. Please note that this 4352 * format is subject to change, and is not intended for serialization. 4353 * 4354 * @return String that looks like 4355 * {@code [IdvActions@HASHCODE: actions=...]}. 4356 */ 4357 @Override public String toString() { 4358 return String.format("[IdvActions@%x: actions=%s]", hashCode(), idToAction); 4359 } 4360 } 4361 4362 /** 4363 * Represents an individual IDV action. Should be fairly adaptable to 4364 * unforeseen changes from Unidata? 4365 */ 4366 // TODO(jon:106): Implement equals/hashCode so that you can use these in Sets. The only relevant value should be the id, right? 4367 public static final class IdvAction { 4368 4369 /** The XML {@link Element} that represents this IDV action. */ 4370 private final Element originalElement; 4371 4372 /** Mapping of (known) XML attributes to values for this individual action. */ 4373 private final Map<ActionAttribute, String> attributes; 4374 4375 /** 4376 * Simple {@literal "cache"} for the different icons this action has 4377 * displayed. This is {@literal "lazy"}, so the cache does not contain 4378 * icons for {@link ToolbarStyle}s that haven't been used. 4379 */ 4380 private final Map<ToolbarStyle, Icon> iconCache = 4381 new ConcurrentHashMap<>(); 4382 4383 /** 4384 * Creates a representation of an IDV action using a given 4385 * {@link Element}. 4386 * 4387 * @param element XML representation of an IDV action. 4388 * Cannot be {@code null}. 4389 * 4390 * @throws NullPointerException if {@code element} is {@code null}. 4391 * @throws IllegalArgumentException if {@code element} is not a valid 4392 * IDV action. 4393 * 4394 * @see UIManager#isValidIdvAction(Element) 4395 */ 4396 public IdvAction(final Element element) { 4397 requireNonNull(element, "Cannot build an action from a null element"); 4398 // TODO(jon:107): need a way to diagnose what's wrong with the action? 4399 Contract.checkArg(isValidIdvAction(element), "Action lacks required attributes"); 4400 originalElement = element; 4401 attributes = actionElementToMap(element); 4402 } 4403 4404 /** 4405 * @return Returns the {@literal "raw"} path to the icon associated 4406 * with this action. Remember that this is actually a 4407 * {@literal "format string"} and should not be considered a valid path! 4408 * 4409 * @see #getIconForStyle 4410 */ 4411 public String getRawIconPath() { 4412 return attributes.get(ActionAttribute.ICON); 4413 } 4414 4415 /** 4416 * @return Returns the {@link Icon} associated with 4417 * {@link ToolbarStyle#SMALL}. 4418 */ 4419 public Icon getMenuIcon() { 4420 return getIconForStyle(ToolbarStyle.SMALL); 4421 } 4422 4423 /** 4424 * Returns the {@link Icon} associated with this action and the given 4425 * {@link ToolbarStyle}. 4426 * 4427 * @param style {@literal "Style"} of the {@code Icon} to be returned. 4428 * Cannot be {@code null}. 4429 * 4430 * @return This action's {@code Icon} with {@code style} 4431 * {@literal "applied."} 4432 * 4433 * @see ActionAttribute#ICON 4434 * @see #iconCache 4435 */ 4436 public Icon getIconForStyle(final ToolbarStyle style) { 4437 requireNonNull(style, "Cannot build an icon for a null ToolbarStyle"); 4438 if (!iconCache.containsKey(style)) { 4439 String styledPath = String.format(getRawIconPath(), style.getSize()); 4440 URL tmp = getClass().getResource(styledPath); 4441 iconCache.put(style, new ImageIcon(Toolkit.getDefaultToolkit().getImage(tmp))); 4442 } 4443 return iconCache.get(style); 4444 } 4445 4446 /** 4447 * @return Returns the identifier of this {@code IdvAction}. 4448 */ 4449 public String getId() { 4450 return getAttribute(ActionAttribute.ID); 4451 } 4452 4453 /** 4454 * Representation of this {@code IdvAction} as an 4455 * {@literal "IDV action call"}. 4456 * 4457 * @return String that is suitable to hand off to the IDV for execution. 4458 */ 4459 public String getCommand() { 4460 return "idv.handleAction('action:"+getAttribute(ActionAttribute.ID)+"')"; 4461 } 4462 4463 /** 4464 * Returns the value associated with a given {@link ActionAttribute} 4465 * for this action. 4466 * 4467 * @param attr ActionAttribute whose value you want. 4468 * Cannot be {@code null}. 4469 * 4470 * @return Value associated with {@code attr}. 4471 * 4472 * @throws NullPointerException if {@code attr} is {@code null}. 4473 */ 4474 public String getAttribute(final ActionAttribute attr) { 4475 requireNonNull(attr, "No values can be associated with a null ActionAttribute"); 4476 return attributes.get(attr); 4477 } 4478 4479 /** 4480 * @return The XML {@link Element} used to create this {@code IdvAction}. 4481 */ 4482 // TODO(jon:104): any way to copy this element? if so, this can become an immutable class! 4483 public Element getElement() { 4484 return originalElement; 4485 } 4486 4487 /** 4488 * Returns a brief description of this action. Please note that the 4489 * format is subject to change and is not intended for serialization. 4490 * 4491 * @return String that looks like 4492 * {@code [IdvAction@HASHCODE: attributes=...]}. 4493 */ 4494 @Override public String toString() { 4495 return String.format("[IdvAction@%x: attributes=%s]", hashCode(), attributes); 4496 } 4497 } 4498}