001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2016 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(new ActionListener() { 1935 public void actionPerformed(ActionEvent ae) { 1936 showBundleDialog(bundleType); 1937 } 1938 }); 1939 1940 final List bundles = getPersistenceManager().getBundles(bundleType); 1941 if (bundles.isEmpty()) { 1942 return; 1943 } 1944 final String title = 1945 getPersistenceManager().getBundleTitle(bundleType); 1946 final String bundleDir = 1947 getPersistenceManager().getBundleDirectory(bundleType); 1948 1949 JMenu bundleMenu = new JMenu(title); 1950 McVGuiUtils.setMenuImage(bundleMenu, Constants.ICON_FAVORITE_SMALL); 1951 bundleMenu.setMnemonic(GuiUtils.charToKeyCode(title)); 1952 1953// getPersistenceManager().initBundleMenu(bundleType, bundleMenu); 1954 1955 Hashtable catMenus = new Hashtable(); 1956 inBundleMenu.addSeparator(); 1957 inBundleMenu.add(bundleMenu); 1958 for (int i = 0; i < bundles.size(); i++) { 1959 SavedBundle bundle = (SavedBundle) bundles.get(i); 1960 List categories = bundle.getCategories(); 1961 JMenu catMenu = bundleMenu; 1962 String mainCategory = ""; 1963 for (int catIdx = 0; catIdx < categories.size(); catIdx++) { 1964 String category = (String) categories.get(catIdx); 1965 mainCategory += "." + category; 1966 JMenu tmpMenu = (JMenu) catMenus.get(mainCategory); 1967 if (tmpMenu == null) { 1968 tmpMenu = new JMenu(category); 1969 catMenu.add(tmpMenu); 1970 catMenus.put(mainCategory, tmpMenu); 1971 } 1972 catMenu = tmpMenu; 1973 } 1974 1975 final SavedBundle theBundle = bundle; 1976 mi = new JMenuItem(bundle.getName()); 1977 mi.addActionListener(new ActionListener() { 1978 public void actionPerformed(ActionEvent ae) { 1979 //Do it in a thread 1980 Misc.run(UIManager.this, "processBundle", theBundle); 1981 } 1982 }); 1983 catMenu.add(mi); 1984 } 1985 } 1986 1987 /** 1988 * Overridden to build a custom Window menu. 1989 * @see ucar.unidata.idv.ui.IdvUIManager#makeWindowsMenu(JMenu, IdvWindow) 1990 */ 1991 @Override public void makeWindowsMenu(final JMenu windowMenu, final IdvWindow idvWindow) { 1992 JMenuItem mi; 1993 boolean first = true; 1994 1995 mi = new JMenuItem("Show Data Explorer"); 1996 McVGuiUtils.setMenuImage(mi, Constants.ICON_DATAEXPLORER_SMALL); 1997 mi.addActionListener(this); 1998 mi.setActionCommand(ACT_SHOW_DASHBOARD); 1999 windowMenu.add(mi); 2000 2001 makeTabNavigationMenu(windowMenu); 2002 2003 @SuppressWarnings("unchecked") // it's how the IDV does it. 2004 List windows = new ArrayList(IdvWindow.getWindows()); 2005 for (int i = 0; i < windows.size(); i++) { 2006 final IdvWindow window = (IdvWindow)windows.get(i); 2007 2008 // Skip the main window 2009 if (window.getIsAMainWindow()) { 2010 continue; 2011 } 2012 2013 String title = window.getTitle(); 2014 String titleParts[] = splitTitle(title); 2015 2016 if (titleParts.length == 2) { 2017 title = titleParts[1]; 2018 } 2019 2020 // Skip the data explorer and display controller 2021 String dataSelectorNameParts[] = splitTitle(Constants.DATASELECTOR_NAME); 2022 if (title.equals(Constants.DATASELECTOR_NAME) || title.equals(dataSelectorNameParts[1])) { 2023 continue; 2024 } 2025 2026 // Add a meaningful name if there is none 2027 if (title.isEmpty()) { 2028 title = "<Unnamed>"; 2029 } 2030 2031 if (window.isVisible()) { 2032 mi = new JMenuItem(title); 2033 mi.addActionListener(new ActionListener() { 2034 public void actionPerformed(ActionEvent ae) { 2035 window.toFront(); 2036 } 2037 }); 2038 2039 if (first) { 2040 windowMenu.addSeparator(); 2041 first = false; 2042 } 2043 2044 windowMenu.add(mi); 2045 } 2046 } 2047 Msg.translateTree(windowMenu); 2048 } 2049 2050 /** 2051 * Add tab navigation {@link JMenuItem JMenuItems} to the given 2052 * {@code menu}. 2053 * 2054 * @param menu Menu to which tab navigation menu items should be added. 2055 * Cannot be {@code null}. 2056 */ 2057 private void makeTabNavigationMenu(final JMenu menu) { 2058 if (!didInitActions) { 2059 didInitActions = true; 2060 initTabNavActions(); 2061 } 2062 2063 if (McVGuiUtils.getAllComponentHolders().size() <= 1) { 2064 return; 2065 } 2066 2067 menu.addSeparator(); 2068 2069 menu.add(new JMenuItem(nextDisplayAction)); 2070 menu.add(new JMenuItem(prevDisplayAction)); 2071 menu.add(new JMenuItem(showDisplayAction)); 2072 2073 if (!McVGuiUtils.getAllComponentGroups().isEmpty()) { 2074 menu.addSeparator(); 2075 } 2076 2077 Msg.translateTree(menu); 2078 } 2079 2080 /** 2081 * Add in the dynamic menu for displaying formulas 2082 * 2083 * @param menu edit menu to add to 2084 */ 2085 public void makeFormulasMenu(JMenu menu) { 2086 MenuUtil.makeMenu(menu, getJythonManager().doMakeFormulaDataSourceMenuItems(null)); 2087 } 2088 2089 /** Whether or not the list of available actions has been initialized. */ 2090 private boolean didInitActions = false; 2091 2092 /** Key combo for the popup with list of displays. */ 2093 private ShowDisplayAction showDisplayAction; 2094 2095 /** 2096 * Key combo for moving to the previous display relative to the current. For 2097 * key combos the lists of displays in the current window is circular. 2098 */ 2099 private PrevDisplayAction prevDisplayAction; 2100 2101 /** 2102 * Key combo for moving to the next display relative to the current. For 2103 * key combos the lists of displays in the current window is circular. 2104 */ 2105 private NextDisplayAction nextDisplayAction; 2106 2107 /** Modifier key, like {@literal "control"} or {@literal "shift"}. */ 2108 private static final String PROP_KB_MODIFIER = "mcidasv.tabbedui.display.kbmodifier"; 2109 2110 /** Key that pops up the list of displays. Used in conjunction with {@code PROP_KB_MODIFIER}. */ 2111 private static final String PROP_KB_SELECT_DISPLAY = "mcidasv.tabbedui.display.kbselect"; 2112 2113 /** Key for moving to the previous display. Used in conjunction with {@code PROP_KB_MODIFIER}. */ 2114 private static final String PROP_KB_DISPLAY_PREV = "mcidasv.tabbedui.display.kbprev"; 2115 2116 /** Key for moving to the next display. Used in conjunction with {@code PROP_KB_MODIFIER}. */ 2117 private static final String PROP_KB_DISPLAY_NEXT = "mcidasv.tabbedui.display.kbnext"; 2118 2119 /** Key for showing the dashboard. Used in conjunction with {@code PROP_KB_MODIFIER}. */ 2120 private static final String PROP_KB_SHOW_DASHBOARD = "mcidasv.tabbedui.display.kbdashboard"; 2121 2122 // TODO: make all this stuff static: mod + acc don't need to read the properties file. 2123 // look at: http://community.livejournal.com/jkff_en/341.html 2124 // look at: effective java, particularly the stuff about enums 2125 private void initTabNavActions() { 2126 String mod = idv.getProperty(PROP_KB_MODIFIER, "control") + " "; 2127 String acc = idv.getProperty(PROP_KB_SELECT_DISPLAY, "L"); 2128 2129 String stroke = mod + acc; 2130 showDisplayAction = new ShowDisplayAction(KeyStroke.getKeyStroke(stroke)); 2131 2132 acc = idv.getProperty(PROP_KB_DISPLAY_PREV, "P"); 2133 stroke = mod + acc; 2134 prevDisplayAction = new PrevDisplayAction(KeyStroke.getKeyStroke(stroke)); 2135 2136 acc = idv.getProperty(PROP_KB_DISPLAY_NEXT, "N"); 2137 stroke = mod + acc; 2138 nextDisplayAction = new NextDisplayAction(KeyStroke.getKeyStroke(stroke)); 2139 } 2140 2141 /** 2142 * Add all the show window keyboard shortcuts. To make keyboard shortcuts 2143 * global, i.e., available no matter what window is active, the appropriate 2144 * actions have to be added the the window contents action and input maps. 2145 * 2146 * FIXME: This can't be the right way to do this! 2147 * 2148 * @param window IdvWindow that requires keyboard shortcut capability. 2149 */ 2150 private void initDisplayShortcuts(IdvWindow window) { 2151 //mjh aug2014 make sure showDisplayAction etc. are initialized: 2152 initTabNavActions(); 2153 didInitActions = true; 2154 2155 JComponent jcomp = window.getContents(); 2156 jcomp.getActionMap().put("show_disp", showDisplayAction); 2157 jcomp.getActionMap().put("prev_disp", prevDisplayAction); 2158 jcomp.getActionMap().put("next_disp", nextDisplayAction); 2159 jcomp.getActionMap().put("show_dashboard", new AbstractAction() { 2160 private static final long serialVersionUID = -364947940824325949L; 2161 public void actionPerformed(ActionEvent evt) { 2162 showDashboard(); 2163 } 2164 }); 2165 2166 String mod = getIdv().getProperty(PROP_KB_MODIFIER, "control"); 2167 String acc = getIdv().getProperty(PROP_KB_SELECT_DISPLAY, "L"); 2168 jcomp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 2169 KeyStroke.getKeyStroke(mod + " " + acc), 2170 "show_disp" 2171 ); 2172 2173 acc = getIdv().getProperty(PROP_KB_SHOW_DASHBOARD, "MINUS"); 2174 jcomp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 2175 KeyStroke.getKeyStroke(mod + " " + acc), 2176 "show_dashboard" 2177 ); 2178 2179 acc = getIdv().getProperty(PROP_KB_DISPLAY_NEXT, "N"); 2180 jcomp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 2181 KeyStroke.getKeyStroke(mod + " " + acc), 2182 "next_disp" 2183 ); 2184 2185 acc = getIdv().getProperty(PROP_KB_DISPLAY_PREV, "P"); 2186 jcomp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 2187 KeyStroke.getKeyStroke(mod + " " + acc), 2188 "prev_disp" 2189 ); 2190 } 2191 2192 /** 2193 * Show Bruce's display selector widget. 2194 */ 2195 protected void showDisplaySelector() { 2196 IdvWindow mainWindow = IdvWindow.getActiveWindow(); 2197 JPanel contents = new JPanel(); 2198 contents.setLayout(new BorderLayout()); 2199 JComponent comp = getDisplaySelectorComponent(); 2200 final JDialog dialog = new JDialog(mainWindow.getFrame(), "List Displays", true); 2201 dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 2202 contents.add(comp, BorderLayout.CENTER); 2203 JButton button = new JButton("OK"); 2204 button.addActionListener(new ActionListener() { 2205 public void actionPerformed(ActionEvent evt) { 2206 final ViewManager vm = getVMManager().getLastActiveViewManager(); 2207 // final DisplayProps disp = getDisplayProps(vm); 2208 // if (disp != null) 2209 // showDisplay(disp); 2210 final McvComponentHolder holder = (McvComponentHolder)getViewManagerHolder(vm); 2211 if (holder != null) { 2212 holder.setAsActiveTab(); 2213 } 2214 2215 // have to do this on the event dispatch thread so we make 2216 // sure it happens after showDisplay 2217 SwingUtilities.invokeLater(new Runnable() { 2218 public void run() { 2219 //setActiveDisplay(disp, disp.managers.indexOf(vm)); 2220 if (holder != null) { 2221 getVMManager().setLastActiveViewManager(vm); 2222 } 2223 } 2224 }); 2225 2226 dialog.dispose(); 2227 } 2228 }); 2229 JPanel buttonPanel = new JPanel(); 2230 buttonPanel.add(button); 2231 dialog.add(buttonPanel, BorderLayout.AFTER_LAST_LINE); 2232 JScrollPane scroller = new JScrollPane(contents); 2233 scroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); 2234 scroller.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); 2235 dialog.add(scroller, BorderLayout.CENTER); 2236 dialog.setSize(200, 300); 2237 dialog.setLocationRelativeTo(mainWindow.getFrame()); 2238 dialog.setVisible(true); 2239 } 2240 2241 private class ShowDisplayAction extends AbstractAction { 2242 private static final long serialVersionUID = -4609753725057124244L; 2243 private static final String ACTION_NAME = "List Displays..."; 2244 public ShowDisplayAction(KeyStroke k) { 2245 super(ACTION_NAME); 2246 putValue(Action.ACCELERATOR_KEY, k); 2247 } 2248 2249 public void actionPerformed(ActionEvent e) { 2250 showDisplaySelector(); 2251 } 2252 } 2253 2254 private class PrevDisplayAction extends AbstractAction { 2255 private static final long serialVersionUID = -3551890663976755671L; 2256 private static final String ACTION_NAME = "Previous Display"; 2257 2258 public PrevDisplayAction(KeyStroke k) { 2259 super(ACTION_NAME); 2260 putValue(Action.ACCELERATOR_KEY, k); 2261 } 2262 2263 public void actionPerformed(ActionEvent e) { 2264 McvComponentHolder prev = (McvComponentHolder)McVGuiUtils.getBeforeActiveHolder(); 2265 if (prev != null) { 2266 prev.setAsActiveTab(); 2267 } 2268 } 2269 } 2270 2271 private class NextDisplayAction extends AbstractAction { 2272 private static final long serialVersionUID = 5431901451767117558L; 2273 private static final String ACTION_NAME = "Next Display"; 2274 2275 public NextDisplayAction(KeyStroke k) { 2276 super(ACTION_NAME); 2277 putValue(Action.ACCELERATOR_KEY, k); 2278 } 2279 2280 public void actionPerformed(ActionEvent e) { 2281 McvComponentHolder next = (McvComponentHolder)McVGuiUtils.getAfterActiveHolder(); 2282 if (next != null) { 2283 next.setAsActiveTab(); 2284 } 2285 } 2286 } 2287 2288 /** 2289 * Populate a "new display" menu from the available skin list. Many thanks 2290 * to Bruce for doing this in the venerable TabbedUIManager. 2291 * 2292 * @param newDisplayMenu menu to populate. 2293 * @param inWindow Is the skinned display to be created in a window? 2294 * 2295 * @see ucar.unidata.idv.IdvResourceManager#RSC_SKIN 2296 * 2297 * @return Menu item populated with display skins 2298 */ 2299 protected JMenuItem doMakeNewDisplayMenu(JMenuItem newDisplayMenu, 2300 final boolean inWindow) 2301 { 2302 if (newDisplayMenu != null) { 2303 2304 String skinFilter = "idv.skin"; 2305 if (!inWindow) { 2306 skinFilter = "mcv.skin"; 2307 } 2308 2309 final XmlResourceCollection skins = 2310 getResourceManager().getXmlResources( 2311 IdvResourceManager.RSC_SKIN); 2312 2313 Map<String, JMenu> menus = new Hashtable<>(); 2314 for (int i = 0; i < skins.size(); i++) { 2315 final Element root = skins.getRoot(i); 2316 if (root == null) { 2317 continue; 2318 } 2319 2320 // filter out mcv or idv skins based on whether or not we're 2321 // interested in tabs or new windows. 2322 final String skinid = skins.getProperty("skinid", i); 2323 if ((skinid != null) && skinid.startsWith(skinFilter)) { 2324 continue; 2325 } 2326 2327 final int skinIndex = i; 2328 List<String> names = 2329 StringUtil.split(skins.getShortName(i), ">", true, true); 2330 2331 JMenuItem theMenu = newDisplayMenu; 2332 String path = ""; 2333 for (int nameIdx = 0; nameIdx < names.size() - 1; nameIdx++) { 2334 String catName = names.get(nameIdx); 2335 path = path + '>' + catName; 2336 JMenu tmpMenu = menus.get(path); 2337 if (tmpMenu == null) { 2338 tmpMenu = new JMenu(catName); 2339 theMenu.add(tmpMenu); 2340 menus.put(path, tmpMenu); 2341 } 2342 theMenu = tmpMenu; 2343 } 2344 2345 final String name = names.get(names.size() - 1); 2346 2347 IdvWindow window = IdvWindow.getActiveWindow(); 2348 for (final McvComponentGroup group : McVGuiUtils.idvGroupsToMcv(window)) { 2349 JMenuItem mi = new JMenuItem(name); 2350 2351 mi.addActionListener(ae -> { 2352 if (!inWindow) { 2353 createNewTab(skinid); 2354 } else { 2355 createNewWindow(null, true, 2356 getStateManager().getTitle(), skins.get( 2357 skinIndex).toString(), skins.getRoot( 2358 skinIndex, false), inWindow, null); 2359 } 2360 }); 2361 theMenu.add(mi); 2362 } 2363 } 2364 2365 // attach the dynamic skin menu item to the tab menu. 2366// if (!inWindow) { 2367// ((JMenu)newDisplayMenu).addSeparator(); 2368// IdvWindow window = IdvWindow.getActiveWindow(); 2369// 2370// final McvComponentGroup group = 2371// (McvComponentGroup)window.getComponentGroups().get(0); 2372// 2373// JMenuItem mi = new JMenuItem("Choose Your Own Adventure..."); 2374// mi.addActionListener(new ActionListener() { 2375// 2376// public void actionPerformed(ActionEvent e) { 2377// makeDynamicSkin(group); 2378// } 2379// }); 2380// newDisplayMenu.add(mi); 2381// } 2382 } 2383 return newDisplayMenu; 2384 } 2385 2386 // for the time being just create some basic viewmanagers. 2387// public void makeDynamicSkin(McvComponentGroup group) { 2388// // 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...) 2389// try { 2390// Document doc = XmlUtil.getDocument(SKIN_TEMPLATE); 2391// Element root = doc.getDocumentElement(); 2392// Element rightChild = doc.createElement("idv.view"); 2393// rightChild.setAttribute("class", "ucar.unidata.idv.TransectViewManager"); 2394// rightChild.setAttribute("viewid", "viewright1337"); 2395// rightChild.setAttribute("id", "viewright"); 2396// 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%;"); 2397// 2398// Element leftChild = doc.createElement("idv.view"); 2399// leftChild.setAttribute("class", "ucar.unidata.idv.MapViewManager"); 2400// leftChild.setAttribute("viewid", "viewleft1337"); 2401// leftChild.setAttribute("id", "viewleft"); 2402// leftChild.setAttribute("properties", "name=Panel 2;clickToFocus=true;showToolBars=true;shareViews=true;showControlLegend=false;size=300:400;shareGroup=view%versionuid%;"); 2403// 2404// Element startNode = XmlUtil.findElement(root, "splitpane", "embeddednode", "true"); 2405// startNode.appendChild(rightChild); 2406// startNode.appendChild(leftChild); 2407// group.makeDynamicSkin(root); 2408// } catch (Exception e) { 2409// LogUtil.logException("Error: parsing skin template:", e); 2410// } 2411// } 2412// 2413// private static final String SKIN_TEMPLATE = 2414// "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + 2415// "<skin embedded=\"true\">\n" + 2416// " <ui>\n" + 2417// " <panel layout=\"border\" bgcolor=\"red\">\n" + 2418// " <idv.menubar place=\"North\"/>\n" + 2419// " <panel layout=\"border\" place=\"Center\">\n" + 2420// " <panel layout=\"flow\" place=\"North\">\n" + 2421// " <idv.toolbar id=\"idv.toolbar\" place=\"West\"/>\n" + 2422// " <panel id=\"idv.favoritesbar\" place=\"North\"/>\n" + 2423// " </panel>\n" + 2424// " <splitpane embeddednode=\"true\" resizeweight=\"0.5\" onetouchexpandable=\"true\" orientation=\"h\" bgcolor=\"blue\" layout=\"grid\" cols=\"2\" place=\"Center\">\n" + 2425// " </splitpane>\n" + 2426// " </panel>\n" + 2427// " <component idref=\"bottom_bar\"/>\n" + 2428// " </panel>\n" + 2429// " </ui>\n" + 2430// " <styles>\n" + 2431// " <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" + 2432// " <style class=\"textbtn\" space=\"2\" mouse_enter=\"ui.setText(idv.messagelabel,prop:tooltip)\" mouse_exit=\"ui.setText(idv.messagelabel,)\"/>\n" + 2433// " </styles>\n" + 2434// " <components>\n" + 2435// " <idv.statusbar place=\"South\" id=\"bottom_bar\"/>\n" + 2436// " </components>\n" + 2437// " <properties>\n" + 2438// " <property name=\"icon.wait.wait\" value=\"/ucar/unidata/idv/images/wait.gif\"/>\n" + 2439// " </properties>\n" + 2440// "</skin>\n"; 2441 2442 private int holderCount; 2443 2444 /** 2445 * Associates a given ViewManager with a given ComponentHolder. 2446 * 2447 * @param vm The ViewManager that is inside {@code holder}. 2448 * @param holder The ComponentHolder that contains {@code vm}. 2449 */ 2450 public void setViewManagerHolder(ViewManager vm, ComponentHolder holder) { 2451 viewManagers.put(vm, holder); 2452 holderCount = getComponentHolders().size(); 2453 } 2454 2455 public Set<ComponentHolder> getComponentHolders() { 2456 return newHashSet(viewManagers.values()); 2457 } 2458 2459 public int getComponentHolderCount() { 2460 return holderCount; 2461 } 2462 2463 public int getComponentGroupCount() { 2464 return getComponentGroups().size(); 2465 } 2466 2467 /** 2468 * Returns the ComponentHolder containing the given ViewManager. 2469 * 2470 * @param vm The ViewManager whose ComponentHolder is needed. 2471 * 2472 * @return Either {@code null} or the {@code ComponentHolder}. 2473 */ 2474 public ComponentHolder getViewManagerHolder(ViewManager vm) { 2475 return viewManagers.get(vm); 2476 } 2477 2478 /** 2479 * Disassociate a given {@code ViewManager} from its 2480 * {@code ComponentHolder}. 2481 * 2482 * @param vm {@code ViewManager} to disassociate. 2483 * 2484 * @return The associated {@code ComponentHolder}. 2485 */ 2486 public ComponentHolder removeViewManagerHolder(ViewManager vm) { 2487 ComponentHolder holder = viewManagers.remove(vm); 2488 holderCount = getComponentHolders().size(); 2489 return holder; 2490 } 2491 2492 /** 2493 * Overridden to keep the dashboard around after it's initially created. 2494 * Also give the user the ability to show a particular tab. 2495 * 2496 * @see ucar.unidata.idv.ui.IdvUIManager#showDashboard() 2497 */ 2498 @Override public void showDashboard() { 2499 showDashboard(""); 2500 } 2501 2502 /** 2503 * Creates the {@link McIDASVViewPanel} component that shows up in the 2504 * dashboard. 2505 * 2506 * @return McIDAS-V specific view panel. 2507 */ 2508 @Override protected ViewPanel doMakeViewPanel() { 2509 ViewPanel vp = new McIDASVViewPanel(idv); 2510 vp.getContents(); 2511 return vp; 2512 } 2513 2514 /** 2515 * Build a mapping of {@literal "skin"} IDs to their indicies within skin 2516 * resources. 2517 * 2518 * @return Map of skin ids to their index within the skin resource. 2519 */ 2520 private Map<String, Integer> readSkinIds() { 2521 XmlResourceCollection skins = 2522 getResourceManager().getXmlResources(IdvResourceManager.RSC_SKIN); 2523 Map<String, Integer> ids = new HashMap<>(skins.size()); 2524 for (int i = 0; i < skins.size(); i++) { 2525 String id = skins.getProperty("skinid", i); 2526 if (id != null) { 2527 ids.put(id, i); 2528 } 2529 } 2530 return ids; 2531 } 2532 2533 /** 2534 * Adds a skinned component holder to the active component group. 2535 * 2536 * @param skinId The value of the skin's skinid attribute. 2537 */ 2538 public void createNewTab(final String skinId) { 2539 IdvWindow activeWindow = IdvWindow.getActiveWindow(); 2540 IdvComponentGroup group = 2541 McVGuiUtils.getComponentGroup(activeWindow); 2542 if (skinIds.containsKey(skinId)) { 2543 group.makeSkin(skinIds.get(skinId)); 2544 } 2545 JFrame frame = activeWindow.getFrame(); 2546 if (frame != null) { 2547 frame.setPreferredSize(frame.getSize()); 2548 } 2549 } 2550 2551 /** 2552 * Method to do the work of showing the Data Explorer (nee Dashboard). 2553 * 2554 * @param tabName Name of the tab that should be made active. 2555 * Cannot be {@code null}, but empty {@code String} values 2556 * will not change the active tab. 2557 */ 2558 @SuppressWarnings("unchecked") // IdvWindow.getWindows only adds IdvWindows. 2559 public void showDashboard(String tabName) { 2560 if (!initDone) { 2561 return; 2562 } else if (dashboard == null) { 2563 showWaitCursor(); 2564 doMakeBasicWindows(); 2565 showNormalCursor(); 2566 String title = makeTitle(getStateManager().getTitle(), Constants.DATASELECTOR_NAME); 2567 for (IdvWindow window : (List<IdvWindow>)IdvWindow.getWindows()) { 2568 if (title.equals(window.getTitle())) { 2569 dashboard = window; 2570 dashboard.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); 2571 } 2572 } 2573 } else { 2574 dashboard.show(); 2575 } 2576 2577 if (tabName.isEmpty()) { 2578 return; 2579 } 2580 2581 // Dig two panels deep looking for a JTabbedPane 2582 // If you find one, try to show the requested tab name 2583 JComponent contents = dashboard.getContents(); 2584 JComponent component = (JComponent)contents.getComponent(0); 2585 JTabbedPane tPane = null; 2586 if (component instanceof JTabbedPane) { 2587 tPane = (JTabbedPane)component; 2588 } 2589 else { 2590 JComponent component2 = (JComponent)component.getComponent(0); 2591 if (component2 instanceof JTabbedPane) { 2592 tPane = (JTabbedPane)component2; 2593 } 2594 } 2595 if (tPane != null) { 2596 for (int i=0; i<tPane.getTabCount(); i++) { 2597 if (tabName.equals(tPane.getTitleAt(i))) { 2598 tPane.setSelectedIndex(i); 2599 break; 2600 } 2601 } 2602 } 2603 } 2604 2605 /** 2606 * Show the support request form 2607 * 2608 * @param description Default value for the description form entry 2609 * @param stackTrace The stack trace that caused this error. 2610 * @param dialog The dialog to put the gui in, if non-null. 2611 */ 2612 public void showSupportForm(final String description, 2613 final String stackTrace, final JDialog dialog) 2614 { 2615 java.awt.EventQueue.invokeLater(() -> { 2616 // TODO: mcvstatecollector should have a way to gather the 2617 // exception information.. 2618 McIDASV mcv = (McIDASV)getIdv(); 2619 new SupportForm(getStore(), new McvStateCollector(mcv)).setVisible(true); 2620 }); 2621 } 2622 2623 /** 2624 * Attempts to locate and display a dashboard component using an ID. 2625 * 2626 * @param id ID of the desired component. 2627 * 2628 * @return True if {@code id} corresponds to a component. False otherwise. 2629 */ 2630 public boolean showDashboardComponent(String id) { 2631 Object comp = findComponent(id); 2632 if (comp != null) { 2633 GuiUtils.showComponentInTabs((JComponent)comp); 2634 return true; 2635 } else { 2636 super.showDashboard(); 2637 for (IdvWindow window : (List<IdvWindow>)IdvWindow.getWindows()) { 2638 String title = makeTitle( 2639 getStateManager().getTitle(), 2640 Constants.DATASELECTOR_NAME 2641 ); 2642 if (title.equals(window.getTitle())) { 2643 dashboard = window; 2644 dashboard.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); 2645 } 2646 } 2647 } 2648 return false; 2649 } 2650 2651 /** 2652 * Close and dispose of the splash window (if it has been created). 2653 */ 2654 @Override 2655 public void splashClose() { 2656 if (splash != null) { 2657 splash.doClose(); 2658 } 2659 } 2660 2661 /** 2662 * Show a message in the splash screen (if it exists) 2663 * 2664 * @param m The message to show 2665 */ 2666 @Override public void splashMsg(String m) { 2667 if (splash != null) { 2668 splash.splashMsg(m); 2669 } 2670 } 2671 2672 /** 2673 * Uses a given toolbar editor to repopulate all toolbars so that they 2674 * correspond to the user's choice of actions. 2675 * 2676 * @param tbe The toolbar editor that contains the actions the user wants. 2677 */ 2678 public void setCurrentToolbars(final McvToolbarEditor tbe) { 2679 List<TwoFacedObject> tfos = tbe.getTLP().getCurrentEntries(); 2680 List<String> buttonIds = new ArrayList<>(tfos.size()); 2681 for (TwoFacedObject tfo : tfos) { 2682 if (McvToolbarEditor.isSpace(tfo)) { 2683 buttonIds.add(null); 2684 } else { 2685 buttonIds.add(TwoFacedObject.getIdString(tfo)); 2686 } 2687 } 2688 2689 cachedButtons = buttonIds; 2690 2691 for (JToolBar toolbar : toolbars) { 2692 toolbar.setVisible(false); 2693 populateToolbar(toolbar); 2694 toolbar.setVisible(true); 2695 } 2696 } 2697 2698 /** 2699 * Append a string and object to the buffer 2700 * 2701 * @param sb StringBuffer to append to 2702 * @param name Name of the object 2703 * @param value the object value 2704 */ 2705 private void append(StringBuffer sb, String name, Object value) { 2706 sb.append("<b>").append(name).append("</b>: ").append(value).append("<br>"); 2707 } 2708 2709 private JMenuItem makeControlDescriptorItem(ControlDescriptor cd) { 2710 JMenuItem mi = new JMenuItem(); 2711 if (cd != null) { 2712 mi = new JMenuItem(cd.getLabel()); 2713 mi.addActionListener(new ObjectListener(cd) { 2714 public void actionPerformed(ActionEvent ev) { 2715 idv.doMakeControl(new ArrayList(), 2716 (ControlDescriptor)theObject); 2717 } 2718 }); 2719 } 2720 return mi; 2721 } 2722 2723 /* (non-javadoc) 2724 * Overridden so that the toolbar will update upon saving a bundle. 2725 */ 2726 @Override public void displayTemplatesChanged() { 2727 super.displayTemplatesChanged(); 2728 for (JToolBar toolbar : toolbars) { 2729 toolbar.setVisible(false); 2730 populateToolbar(toolbar); 2731 toolbar.setVisible(true); 2732 } 2733 } 2734 2735 /** 2736 * Called when there has been any change to the favorite bundles and is 2737 * most useful for triggering an update to the {@literal "toolbar bundles"}. 2738 */ 2739 @Override public void favoriteBundlesChanged() { 2740 SwingUtilities.invokeLater(() -> { 2741 for (JToolBar toolbar : toolbars) { 2742 toolbar.setVisible(false); 2743 populateToolbar(toolbar); 2744 toolbar.setVisible(true); 2745 } 2746 }); 2747 } 2748 2749 /** 2750 * Show the support request form in a non-swing thread. We do this because we cannot 2751 * call the HttpFormEntry.showUI from a swing thread 2752 * 2753 * @param description Default value for the description form entry 2754 * @param stackTrace The stack trace that caused this error. 2755 * @param dialog The dialog to put the gui in, if non-null. 2756 */ 2757 2758 private void showSupportFormInThread(String description, 2759 String stackTrace, JDialog dialog) { 2760 List<HttpFormEntry> entries = new ArrayList<>(); 2761 2762 StringBuffer extra = new StringBuffer("<h3>McIDAS-V</h3>\n"); 2763 Hashtable<String, String> table = 2764 ((StateManager)getStateManager()).getVersionInfo(); 2765 append(extra, "mcv.version.general", table.get("mcv.version.general")); 2766 append(extra, "mcv.version.build", table.get("mcv.version.build")); 2767 append(extra, "idv.version.general", table.get("idv.version.general")); 2768 append(extra, "idv.version.build", table.get("idv.version.build")); 2769 2770 extra.append("<h3>OS</h3>\n"); 2771 append(extra, "os.name", System.getProperty("os.name")); 2772 append(extra, "os.arch", System.getProperty("os.arch")); 2773 append(extra, "os.version", System.getProperty("os.version")); 2774 2775 extra.append("<h3>Java</h3>\n"); 2776 append(extra, "java.vendor", System.getProperty("java.vendor")); 2777 append(extra, "java.version", System.getProperty("java.version")); 2778 append(extra, "java.home", System.getProperty("java.home")); 2779 2780 StringBuffer javaInfo = new StringBuffer(); 2781 javaInfo.append("Java: home: " + System.getProperty("java.home")); 2782 javaInfo.append(" version: " + System.getProperty("java.version")); 2783 2784 Class c = null; 2785 try { 2786 c = Class.forName("javax.media.j3d.VirtualUniverse"); 2787 Method method = Misc.findMethod(c, "getProperties", 2788 new Class[] {}); 2789 if (method == null) { 2790 javaInfo.append("j3d <1.3"); 2791 } else { 2792 try { 2793 Map m = (Map)method.invoke(c, new Object[] {}); 2794 javaInfo.append(" j3d:" + m.get("j3d.version")); 2795 append(extra, "j3d.version", m.get("j3d.version")); 2796 append(extra, "j3d.vendor", m.get("j3d.vendor")); 2797 append(extra, "j3d.renderer", m.get("j3d.renderer")); 2798 } catch (Exception exc) { 2799 javaInfo.append(" j3d:" + "unknown"); 2800 } 2801 } 2802 } catch (ClassNotFoundException exc) { 2803 append(extra, "j3d", "none"); 2804 } 2805 2806 boolean persistCC = getStore().get("mcv.supportreq.cc", true); 2807 2808 JCheckBox ccMyself = new JCheckBox("Send Copy of Support Request to Me", persistCC); 2809 ccMyself.addActionListener(e -> { 2810 JCheckBox cb = (JCheckBox)e.getSource(); 2811 getStore().put("mcv.supportreq.cc", cb.isSelected()); 2812 }); 2813 2814 boolean doWrap = idv.getProperty(PROP_WRAP_SUPPORT_DESC, true); 2815 2816 HttpFormEntry descriptionEntry; 2817 HttpFormEntry nameEntry; 2818 HttpFormEntry emailEntry; 2819 HttpFormEntry orgEntry; 2820 2821 entries.add(nameEntry = new HttpFormEntry(HttpFormEntry.TYPE_INPUT, 2822 "form_data[fromName]", "Name:", 2823 getStore().get(PROP_HELP_NAME, (String) null))); 2824 entries.add(emailEntry = new HttpFormEntry(HttpFormEntry.TYPE_INPUT, 2825 "form_data[email]", "Your Email:", 2826 getStore().get(PROP_HELP_EMAIL, (String) null))); 2827 entries.add(orgEntry = new HttpFormEntry(HttpFormEntry.TYPE_INPUT, 2828 "form_data[organization]", "Organization:", 2829 getStore().get(PROP_HELP_ORG, (String) null))); 2830 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_INPUT, 2831 "form_data[subject]", "Subject:")); 2832 2833 entries.add( 2834 new HttpFormEntry( 2835 HttpFormEntry.TYPE_LABEL, "", 2836 "<html>Please provide a <i>thorough</i> description of the problem you encountered:</html>")); 2837 entries.add(descriptionEntry = 2838 new FormEntry(doWrap, HttpFormEntry.TYPE_AREA, 2839 "form_data[description]", "Description:", 2840 description, 5, 30, true)); 2841 2842 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_FILE, 2843 "form_data[att_two]", "Attachment 1:", "", 2844 false)); 2845 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_FILE, 2846 "form_data[att_three]", "Attachment 2:", "", 2847 false)); 2848 2849 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_HIDDEN, 2850 "form_data[submit]", "", "Send Email")); 2851 2852 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_HIDDEN, 2853 "form_data[p_version]", "", 2854 getStateManager().getVersion() 2855 + " build date:" 2856 + getStateManager().getBuildDate())); 2857 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_HIDDEN, 2858 "form_data[opsys]", "", 2859 System.getProperty("os.name"))); 2860 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_HIDDEN, 2861 "form_data[hardware]", "", 2862 javaInfo.toString())); 2863 2864 JLabel topLabel = 2865 new JLabel("<html>This form allows you to send a support request to the McIDAS Help Desk.<br></html>"); 2866 2867 JCheckBox includeBundleCbx = 2868 new JCheckBox("Include Current State as Bundle", false); 2869 2870 List<JCheckBox> checkboxes = list(includeBundleCbx, ccMyself); 2871 2872 boolean alreadyHaveDialog = true; 2873 if (dialog == null) { 2874 // NOTE: if the dialog is modeless you can leave alreadyHaveDialog 2875 // alone. If the dialog is modal you need to set alreadyHaveDialog 2876 // to false. 2877 // If alreadyHaveDialog is false with a modeless dialog, the later 2878 // call to HttpFormEntry.showUI will return false and break out of 2879 // the while loop without talking to the HTTP server. 2880 dialog = GuiUtils.createDialog(LogUtil.getCurrentWindow(), 2881 "Support Request Form", false); 2882// alreadyHaveDialog = false; 2883 } 2884 2885 JLabel statusLabel = GuiUtils.cLabel(" "); 2886 JComponent bottom = LayoutUtil.vbox(LayoutUtil.leftVbox(checkboxes), statusLabel); 2887 2888 while (true) { 2889 //Show form. Check if user pressed cancel. 2890 statusLabel.setText(" "); 2891 if ( !HttpFormEntry.showUI(entries, LayoutUtil.inset(topLabel, 10), 2892 bottom, dialog, alreadyHaveDialog)) { 2893 break; 2894 } 2895 statusLabel.setText("Posting support request..."); 2896 2897 //Save persistent state 2898 getStore().put(PROP_HELP_NAME, nameEntry.getValue()); 2899 getStore().put(PROP_HELP_ORG, orgEntry.getValue()); 2900 getStore().put(PROP_HELP_EMAIL, emailEntry.getValue()); 2901 getStore().save(); 2902 2903 List<HttpFormEntry> entriesToPost = 2904 new ArrayList<>(entries); 2905 2906 if ((stackTrace != null) && (stackTrace.length() > 0)) { 2907 entriesToPost.remove(descriptionEntry); 2908 String newDescription = 2909 descriptionEntry.getValue() 2910 + "\n\n******************\nStack trace:\n" + stackTrace; 2911 entriesToPost.add( 2912 new HttpFormEntry( 2913 HttpFormEntry.TYPE_HIDDEN, "form_data[description]", 2914 "Description:", newDescription, 5, 30, true)); 2915 } 2916 2917 try { 2918 extra.append(idv.getPluginManager().getPluginHtml()); 2919 extra.append(getResourceManager().getHtmlView()); 2920 2921 entriesToPost.add(new HttpFormEntry("form_data[att_extra]", 2922 "extra.html", extra.toString().getBytes())); 2923 2924 if (includeBundleCbx.isSelected()) { 2925 entriesToPost.add( 2926 new HttpFormEntry( 2927 "form_data[att_state]", "bundle" + Constants.SUFFIX_MCV, 2928 idv.getPersistenceManager().getBundleXml( 2929 true).getBytes())); 2930 } 2931 entriesToPost.add(new HttpFormEntry(HttpFormEntry.TYPE_HIDDEN, 2932 "form_data[cc_user]", "", 2933 Boolean.toString(getStore().get("mcv.supportreq.cc", true)))); 2934 2935 String[] results = 2936 HttpFormEntry.doPost(entriesToPost, SUPPORT_REQ_URL); 2937 2938 if (results[0] != null) { 2939 GuiUtils.showHtmlDialog( 2940 results[0], "Support Request Response - Error", 2941 "Support Request Response - Error", null, true); 2942 continue; 2943 } 2944 String html = results[1]; 2945 if (html.toLowerCase().indexOf("your email has been sent") 2946 >= 0) { 2947 LogUtil.userMessage("Your support request has been sent"); 2948 break; 2949 } else if (html.toLowerCase().indexOf("required fields") 2950 >= 0) { 2951 LogUtil.userErrorMessage( 2952 "<html>There was a problem submitting your request. <br>Is your email correct?</html>"); 2953 } else { 2954 GuiUtils.showHtmlDialog( 2955 html, "Unknown Support Request Response", 2956 "Unknown Support Request Response", null, true); 2957 System.err.println(html.toLowerCase()); 2958 } 2959 } catch (Exception exc) { 2960 LogUtil.logException("Doing support request form", exc); 2961 } 2962 } 2963 dialog.dispose(); 2964 } 2965 2966 @Override protected IdvXmlUi doMakeIdvXmlUi(IdvWindow window, 2967 List viewManagers, Element skinRoot) 2968 { 2969 return new McIDASVXmlUi(window, viewManagers, idv, skinRoot); 2970 } 2971 2972 /** 2973 * DeInitialize the given menu before it is shown 2974 * @see ucar.unidata.idv.ui.IdvUIManager#historyMenuSelected(JMenu) 2975 */ 2976 @Override 2977 protected void handleMenuDeSelected(final String id, final JMenu menu, final IdvWindow idvWindow) { 2978 super.handleMenuDeSelected(id, menu, idvWindow); 2979 } 2980 2981 /** 2982 * Initialize the given menu before it is shown 2983 * @see ucar.unidata.idv.ui.IdvUIManager#historyMenuSelected(JMenu) 2984 */ 2985 @Override 2986 protected void handleMenuSelected(final String id, final JMenu menu, final IdvWindow idvWindow) { 2987 if (id.equals(MENU_NEWVIEWS)) { 2988 ViewManager last = getVMManager().getLastActiveViewManager(); 2989 menu.removeAll(); 2990 makeViewStateMenu(menu, last); 2991 } else if (id.equals("bundles")) { 2992 menu.removeAll(); 2993 makeBundleMenu(menu); 2994 } else if (id.equals(MENU_NEWDISPLAY_TAB)) { 2995 menu.removeAll(); 2996 doMakeNewDisplayMenu(menu, false); 2997 } else if (id.equals(MENU_NEWDISPLAY)) { 2998 menu.removeAll(); 2999 doMakeNewDisplayMenu(menu, true); 3000 } else if (id.equals("menu.tools.projections.deletesaved")) { 3001 menu.removeAll(); 3002 makeDeleteViewsMenu(menu); 3003 } else if (id.equals("file.default.layout")) { 3004 makeDefaultLayoutMenu(menu); 3005 } else if (id.equals("tools.formulas")) { 3006 menu.removeAll(); 3007 makeFormulasMenu(menu); 3008 } else { 3009 super.handleMenuSelected(id, menu, idvWindow); 3010 } 3011 } 3012 3013 /** A cache of the operand name to value for the user choices */ 3014 private Map operandCache; 3015 3016 @Override public List selectUserChoices(String msg, List userOperands) { 3017 if (operandCache == null) { 3018 operandCache = 3019 (Hashtable) getStore().getEncodedFile("operandcache.xml"); 3020 if (operandCache == null) { 3021 operandCache = new Hashtable(); 3022 } 3023 } 3024 List fields = new ArrayList(); 3025 List components = new ArrayList(); 3026 List persistentCbxs = new ArrayList(); 3027 components.add(new JLabel("Property")); 3028 components.add(new JLabel("Value")); 3029 components.add(new JLabel("Save in Bundle")); 3030 for (int i = 0; i < userOperands.size(); i++) { 3031 DataOperand operand = (DataOperand)userOperands.get(i); 3032 String fieldType = operand.getProperty("type"); 3033 if (fieldType == null) { 3034 fieldType = FIELDTYPE_TEXT; 3035 } 3036 DerivedDataChoice formula = operand.getDataChoice(); 3037 String description = operand.getDescription(); 3038 if (formula != null) { 3039 description = formula.toString(); 3040 } 3041 3042 String label = operand.getLabel(); 3043 Object dflt = operand.getUserDefault(); 3044 Object cacheKeyNewStyle = Misc.newList(description, label, fieldType); 3045 Object cacheKey = Misc.newList(label, fieldType); 3046 3047 Object cachedOperand = null; 3048 boolean oldStyle = operandCache.containsKey(cacheKey); 3049 boolean newStyle = operandCache.containsKey(cacheKeyNewStyle); 3050 3051 // if new style, always use that and ignore old style 3052 // if no new style, proceed as before. 3053 if (newStyle) { 3054 cachedOperand = operandCache.get(cacheKeyNewStyle); 3055 } else if (oldStyle) { 3056 cachedOperand = operandCache.get(cacheKey); 3057 } 3058 3059 if (cachedOperand != null) { 3060 dflt = cachedOperand; 3061 } 3062 3063 JCheckBox cbx = new JCheckBox("", operand.isPersistent()); 3064 persistentCbxs.add(cbx); 3065 JComponent field = null; 3066 JComponent fieldComp = null; 3067 if (fieldType.equals(FIELDTYPE_TEXT)) { 3068 String rowString = operand.getProperty("rows"); 3069 if (rowString == null) { 3070 rowString = "1"; 3071 } 3072 int rows = new Integer(rowString).intValue(); 3073 if (rows == 1) { 3074 field = new JTextField((dflt != null) 3075 ? dflt.toString() 3076 : "", 15); 3077 } else { 3078 field = new JTextArea((dflt != null) 3079 ? dflt.toString() 3080 : "", rows, 15); 3081 fieldComp = GuiUtils.makeScrollPane(field, 200, 100); 3082 } 3083 } else if (fieldType.equals(FIELDTYPE_BOOLEAN)) { 3084 field = new JCheckBox("", ((dflt != null) 3085 ? new Boolean( 3086 dflt.toString()).booleanValue() 3087 : true)); 3088 } else if (fieldType.equals(FIELDTYPE_CHOICE)) { 3089 String choices = operand.getProperty("choices"); 3090 if (choices == null) { 3091 throw new IllegalArgumentException( 3092 "No 'choices' attribute defined for operand: " 3093 + operand); 3094 } 3095 List l = StringUtil.split(choices, ";", true, true); 3096 field = new JComboBox(new Vector(l)); 3097 if ((dflt != null) && l.contains(dflt)) { 3098 ((JComboBox) field).setSelectedItem(dflt); 3099 } 3100 } else if (fieldType.equals(FIELDTYPE_FILE)) { 3101 JTextField fileFld = new JTextField(((dflt != null) 3102 ? dflt.toString() 3103 : ""), 30); 3104 field = fileFld; 3105 String patterns = operand.getProperty("filepattern"); 3106 List filters = null; 3107 if (patterns != null) { 3108 filters = new ArrayList(); 3109 List toks = StringUtil.split(patterns, ";", true, true); 3110 for (int tokIdx = 0; tokIdx < toks.size(); tokIdx++) { 3111 String tok = (String) toks.get(tokIdx); 3112 List subToks = StringUtil.split(tok, ":", true, true); 3113 if (subToks.size() == 2) { 3114 filters.add( 3115 new PatternFileFilter( 3116 (String)subToks.get(0), 3117 (String)subToks.get(1))); 3118 } else { 3119 filters.add(new PatternFileFilter(tok, tok)); 3120 } 3121 } 3122 } 3123 fieldComp = GuiUtils.centerRight(GuiUtils.hfill(fileFld), 3124 GuiUtils.makeFileBrowseButton(fileFld, filters)); 3125 } else if (fieldType.equals(FIELDTYPE_LOCATION)) { 3126 List l = ((dflt != null) 3127 ? StringUtil.split(dflt.toString(), ";", true, true) 3128 : (List) new ArrayList()); 3129 final LatLonWidget llw = new LatLonWidget(); 3130 field = llw; 3131 if (l.size() == 2) { 3132 llw.setLat(Misc.decodeLatLon(l.get(0).toString())); 3133 llw.setLon(Misc.decodeLatLon(l.get(1).toString())); 3134 } 3135 final JButton centerPopupBtn = 3136 GuiUtils.getImageButton("/auxdata/ui/icons/Map16.gif", 3137 getClass()); 3138 centerPopupBtn.setToolTipText("Center on current displays"); 3139 centerPopupBtn.addActionListener(ae -> popupCenterMenu(centerPopupBtn, llw)); 3140 JComponent centerPopup = GuiUtils.inset(centerPopupBtn, 3141 new Insets(0, 0, 0, 4)); 3142 fieldComp = GuiUtils.hbox(llw, centerPopup); 3143 } else if (fieldType.equals(FIELDTYPE_AREA)) { 3144 //TODO: 3145 } else { 3146 throw new IllegalArgumentException("Unknown type: " 3147 + fieldType + " for operand: " + operand); 3148 } 3149 3150 fields.add(field); 3151 label = StringUtil.replace(label, "_", " "); 3152 components.add(GuiUtils.rLabel(label)); 3153 components.add((fieldComp != null) 3154 ? fieldComp 3155 : field); 3156 components.add(cbx); 3157 } 3158 // GuiUtils.tmpColFills = new int[] { GridBagConstraints.HORIZONTAL, 3159 // GridBagConstraints.NONE, 3160 // GridBagConstraints.NONE }; 3161 GuiUtils.tmpInsets = GuiUtils.INSETS_5; 3162 Component contents = GuiUtils.topCenter(new JLabel(msg), 3163 GuiUtils.doLayout(components, 3, 3164 GuiUtils.WT_NYN, GuiUtils.WT_N)); 3165 if ( !GuiUtils.showOkCancelDialog(null, "Select input", contents, 3166 null, fields)) { 3167 return null; 3168 } 3169 List values = new ArrayList(); 3170 for (int i = 0; i < userOperands.size(); i++) { 3171 DataOperand operand = (DataOperand) userOperands.get(i); 3172 String description = operand.getDescription(); 3173 DerivedDataChoice formula = operand.getDataChoice(); 3174 String label = operand.getLabel(); 3175 Object field = fields.get(i); 3176 Object value = null; 3177 Object cacheValue = null; 3178 3179 if (formula != null) { 3180 description = formula.toString(); 3181 } 3182 3183 if (field instanceof JTextComponent) { 3184 value = ((JTextComponent) field).getText().trim(); 3185 } else if (field instanceof JCheckBox) { 3186 value = new Boolean(((JCheckBox)field).isSelected()); 3187 } else if (field instanceof JComboBox) { 3188 value = ((JComboBox) field).getSelectedItem(); 3189 } else if (field instanceof LatLonWidget) { 3190 LatLonWidget llw = (LatLonWidget) field; 3191 value = new LatLonPointImpl(llw.getLat(), llw.getLon()); 3192 cacheValue = llw.getLat() + ";" + llw.getLon(); 3193 } else { 3194 throw new IllegalArgumentException("Unknown field type:" 3195 + field.getClass().getName()); 3196 } 3197 if (cacheValue == null) { 3198 cacheValue = value; 3199 } 3200 JCheckBox cbx = (JCheckBox)persistentCbxs.get(i); 3201 String fieldType = operand.getProperty("type"); 3202 if (fieldType == null) { 3203 fieldType = "text"; 3204 } 3205 3206 Object cacheKey = Misc.newList(description, label, fieldType); 3207 operandCache.put(cacheKey, cacheValue); 3208 values.add(new UserOperandValue(value, cbx.isSelected())); 3209 } 3210 getStore().putEncodedFile("operandcache.xml", operandCache); 3211 return values; 3212 } 3213 3214 private boolean didTabs = false; 3215 private boolean didNewWindow = false; 3216 3217 public void makeDefaultLayoutMenu(final JMenu menu) { 3218 if (menu == null) 3219 throw new NullPointerException("Must provide a non-null default layout menu"); 3220 3221 menu.removeAll(); 3222 JMenuItem saveLayout = new JMenuItem("Save"); 3223 McVGuiUtils.setMenuImage(saveLayout, Constants.ICON_DEFAULTLAYOUTADD_SMALL); 3224 saveLayout.setToolTipText("Save as default layout"); 3225 saveLayout.addActionListener(e -> ((McIDASV)idv).doSaveAsDefaultLayout()); 3226 3227 JMenuItem removeLayout = new JMenuItem("Remove"); 3228 McVGuiUtils.setMenuImage(removeLayout, Constants.ICON_DEFAULTLAYOUTDELETE_SMALL); 3229 removeLayout.setToolTipText("Remove saved default layout"); 3230 removeLayout.addActionListener(e -> idv.doClearDefaults()); 3231 3232 removeLayout.setEnabled(((McIDASV)idv).hasDefaultLayout()); 3233 3234 menu.add(saveLayout); 3235 menu.add(removeLayout); 3236 } 3237 3238 /** 3239 * Bundles any compatible {@link ViewManager} states into 3240 * {@link JMenuItem JMenuItem} and adds said menu items to {@code menu}. 3241 * Incompatible states are ignored. 3242 * 3243 * <p>Each {@code JMenuItem} (except those under the {@literal "Delete"} 3244 * menu--apologies) associates a {@literal "view state"} and an 3245 * {@link ObjectListener}. The {@code ObjectListener} uses this associated 3246 * view state to attempt reinitialization of {@code vm}. 3247 * 3248 * <p>Override reasoning: 3249 * <ul> 3250 * <li> 3251 * terminology ({@literal "views"} rather than {@literal "viewpoints"}). 3252 * </li> 3253 * <li> 3254 * use of {@link #filterVMMStatesWithVM(ViewManager, Collection)} to 3255 * properly detect the {@literal "no saved views"} case. 3256 * </li> 3257 * </ul> 3258 * 3259 * @param menu Menu to populate. Should not be {@code null}. 3260 * @param vm {@code ViewManager} that might get reinitialized. 3261 * Should not be {@code null}. 3262 * 3263 * @see ViewManager#initWith(ViewManager, boolean) 3264 * @see ViewManager#initWith(ViewState) 3265 */ 3266 @Override public void makeViewStateMenu(final JMenu menu, final ViewManager vm) { 3267 List<TwoFacedObject> vmStates = 3268 filterVMMStatesWithVM(vm, getVMManager().getVMState()); 3269 if (vmStates.isEmpty()) { 3270 JMenuItem item = new JMenuItem(Msg.msg("No Saved Views")); 3271 item.setEnabled(false); 3272 menu.add(item); 3273 } else { 3274 JMenu deleteMenu = new JMenu("Delete"); 3275 makeDeleteViewsMenu(deleteMenu); 3276 menu.add(deleteMenu); 3277 } 3278 3279 for (TwoFacedObject tfo : vmStates) { 3280 JMenuItem mi = new JMenuItem(tfo.getLabel().toString()); 3281 menu.add(mi); 3282 mi.addActionListener(new ObjectListener(tfo.getId()) { 3283 public void actionPerformed(final ActionEvent e) { 3284 if (vm == null) { 3285 return; 3286 } 3287 3288 if (theObject instanceof ViewManager) { 3289 vm.initWith((ViewManager)theObject, true); 3290 } else if (theObject instanceof ViewState) { 3291 try { 3292 vm.initWith((ViewState)theObject); 3293 } catch (Throwable ex) { 3294 logException("Initializing view with ViewState", ex); 3295 } 3296 } else { 3297 LogUtil.consoleMessage("UIManager.makeViewStateMenu: Object of unknown type: "+theObject.getClass().getName()); 3298 } 3299 } 3300 }); 3301 } 3302 3303 // the "3" ensures that the "save viewpoint" menu item, the separator, 3304 // and the "delete" menu item are fixed at the top. 3305 new MenuScroller(menu, menu, 125, 3); 3306 } 3307 3308 /** 3309 * Overridden by McIDAS-V to add menu scrolling functionality to the 3310 * {@literal "delete"} submenu. 3311 * 3312 * @param menu {@literal "Delete"} submenu. 3313 */ 3314 @Override public void makeDeleteViewsMenu(JMenu menu) { 3315 super.makeDeleteViewsMenu(menu); 3316 new MenuScroller(menu, menu, 125); 3317 } 3318 3319 /** 3320 * Returns a list of {@link TwoFacedObject}s that are known to be 3321 * compatible with {@code vm}. 3322 * 3323 * <p>This method is currently capable of dealing with 3324 * {@link TwoFacedObject TwoFacedObjects} and 3325 * {@link ViewState ViewStates} within {@code states}. Any other types are 3326 * ignored. 3327 * 3328 * @param vm {@link ViewManager} to use for compatibility tests. 3329 * {@code null} is allowed. 3330 * @param states Collection of objects to test against {@code vm}. 3331 * {@code null} is allowed. 3332 * 3333 * @return Either a {@link List} of compatible {@literal "view states"} 3334 * or an empty {@code List}. 3335 * 3336 * @see ViewManager#isCompatibleWith(ViewManager) 3337 * @see ViewManager#isCompatibleWith(ViewState) 3338 * @see #makeViewStateMenu(JMenu, ViewManager) 3339 */ 3340 public static List<TwoFacedObject> filterVMMStatesWithVM(final ViewManager vm, final Collection<?> states) { 3341 if ((vm == null) || (states == null) || states.isEmpty()) { 3342 return Collections.emptyList(); 3343 } 3344 3345 List<TwoFacedObject> validStates = new ArrayList<>(states.size()); 3346 for (Object obj : states) { 3347 TwoFacedObject tfo = null; 3348 if (obj instanceof TwoFacedObject) { 3349 tfo = (TwoFacedObject)obj; 3350 if (vm.isCompatibleWith((ViewManager)tfo.getId())) { 3351 continue; 3352 } 3353 } else if (obj instanceof ViewState) { 3354 if (!vm.isCompatibleWith((ViewState)obj)) { 3355 continue; 3356 } 3357 tfo = new TwoFacedObject(((ViewState)obj).getName(), obj); 3358 } else { 3359 LogUtil.consoleMessage("UIManager.filterVMMStatesWithVM: Object of unknown type: "+obj.getClass().getName()); 3360 continue; 3361 } 3362 validStates.add(tfo); 3363 } 3364 return validStates; 3365 } 3366 3367 /** 3368 * Overridden to build a custom Display menu. 3369 * @see ucar.unidata.idv.ui.IdvUIManager#initializeDisplayMenu(JMenu) 3370 */ 3371 @Override protected void initializeDisplayMenu(JMenu displayMenu) { 3372 JMenu m; 3373 JMenuItem mi; 3374 3375 // Get the list of possible standalone control descriptors 3376 Hashtable controlsHash = new Hashtable(); 3377 List controlDescriptors = getStandAloneControlDescriptors(); 3378 for (int i = 0; i < controlDescriptors.size(); i++) { 3379 ControlDescriptor cd = (ControlDescriptor)controlDescriptors.get(i); 3380 String cdLabel = cd.getLabel(); 3381 if (cdLabel.equals("Range Rings")) { 3382 controlsHash.put(cdLabel, cd); 3383 } else if (cdLabel.equals("Range and Bearing")) { 3384 controlsHash.put(cdLabel, cd); 3385 } else if (cdLabel.equals("Location Indicator")) { 3386 controlsHash.put(cdLabel, cd); 3387 } else if (cdLabel.equals("Drawing Control")) { 3388 controlsHash.put(cdLabel, cd); 3389 } else if (cdLabel.equals("Transect Drawing Control")) { 3390 controlsHash.put(cdLabel, cd); 3391 } 3392 } 3393 3394 // Build the menu 3395 ControlDescriptor cd; 3396 3397 mi = new JMenuItem("Create Layer from Data Source..."); 3398 mi.addActionListener(ae -> showDashboard("Data Sources")); 3399 displayMenu.add(mi); 3400 3401 mi = new JMenuItem("Layer Controls..."); 3402 mi.addActionListener(ae -> showDashboard("Layer Controls")); 3403 displayMenu.add(mi); 3404 3405 displayMenu.addSeparator(); 3406 3407 cd = (ControlDescriptor)controlsHash.get("Range Rings"); 3408 mi = makeControlDescriptorItem(cd); 3409 mi.setText("Add Range Rings"); 3410 displayMenu.add(mi); 3411 3412 cd = (ControlDescriptor)controlsHash.get("Range and Bearing"); 3413 mi = makeControlDescriptorItem(cd); 3414 McVGuiUtils.setMenuImage(mi, Constants.ICON_RANGEANDBEARING_SMALL); 3415 mi.setText("Add Range and Bearing"); 3416 displayMenu.add(mi); 3417 3418 displayMenu.addSeparator(); 3419 3420 cd = (ControlDescriptor)controlsHash.get("Transect Drawing Control"); 3421 mi = makeControlDescriptorItem(cd); 3422 mi.setText("Draw Transect..."); 3423 displayMenu.add(mi); 3424 3425 cd = (ControlDescriptor)controlsHash.get("Drawing Control"); 3426 mi = makeControlDescriptorItem(cd); 3427 mi.setText("Draw Freely..."); 3428 displayMenu.add(mi); 3429 3430 displayMenu.addSeparator(); 3431 3432 cd = (ControlDescriptor)controlsHash.get("Location Indicator"); 3433 mi = makeControlDescriptorItem(cd); 3434 McVGuiUtils.setMenuImage(mi, Constants.ICON_LOCATION_SMALL); 3435 mi.setText("Add Location Indicator"); 3436 displayMenu.add(mi); 3437 3438 ControlDescriptor locationDescriptor = idv.getControlDescriptor("locationcontrol"); 3439 if (locationDescriptor != null) { 3440 List stations = idv.getLocationList(); 3441 ObjectListener listener = new ObjectListener(locationDescriptor) { 3442 public void actionPerformed(ActionEvent ae, Object obj) { 3443 addStationDisplay((NamedStationTable) obj, (ControlDescriptor) theObject); 3444 } 3445 }; 3446 List menuItems = NamedStationTable.makeMenuItems(stations, listener); 3447 displayMenu.add(MenuUtil.makeMenu("Plot Location Labels", menuItems)); 3448 } 3449 3450 displayMenu.addSeparator(); 3451 3452 mi = new JMenuItem("Add Background Image"); 3453 McVGuiUtils.setMenuImage(mi, Constants.ICON_BACKGROUND_SMALL); 3454 mi.addActionListener(ae -> getIdv().doMakeBackgroundImage()); 3455 displayMenu.add(mi); 3456 3457 mi = new JMenuItem("Reset Map Layer to Defaults"); 3458 mi.addActionListener(ae -> { 3459 // TODO: Call IdvUIManager.addDefaultMap()... should be made private 3460// addDefaultMap(); 3461 ControlDescriptor mapDescriptor = idv.getControlDescriptor("mapdisplay"); 3462 if (mapDescriptor == null) { 3463 return; 3464 } 3465 String attrs = "initializeAsDefault=true;displayName=Default Background Maps;"; 3466 idv.doMakeControl(new ArrayList(), mapDescriptor, attrs, null); 3467 }); 3468 displayMenu.add(mi); 3469 Msg.translateTree(displayMenu); 3470 } 3471 3472 /** 3473 * Get the window title from the skin 3474 * 3475 * @param index the skin index 3476 * 3477 * @return the title 3478 */ 3479 private String getWindowTitleFromSkin(final int index) { 3480 if (!skinToTitle.containsKey(index)) { 3481 IdvResourceManager mngr = getResourceManager(); 3482 XmlResourceCollection skins = mngr.getXmlResources(mngr.RSC_SKIN); 3483 List<String> names = StringUtil.split(skins.getShortName(index), ">", true, true); 3484 String title = getStateManager().getTitle(); 3485 if (!names.isEmpty()) { 3486 title = title + " - " + StringUtil.join(" - ", names); 3487 } 3488 skinToTitle.put(index, title); 3489 } 3490 return skinToTitle.get(index); 3491 } 3492 3493 @SuppressWarnings("unchecked") 3494 @Override public Hashtable getMenuIds() { 3495 return menuIds; 3496 } 3497 3498 @SuppressWarnings("unchecked") 3499 @Override public JMenuBar doMakeMenuBar(final IdvWindow idvWindow) { 3500 Hashtable<String, JMenuItem> menuMap = new Hashtable<>(); 3501 JMenuBar menuBar = new JMenuBar(); 3502 final IdvResourceManager mngr = getResourceManager(); 3503 XmlResourceCollection xrc = mngr.getXmlResources(mngr.RSC_MENUBAR); 3504 Hashtable<String, ImageIcon> actionIcons = new Hashtable<>(); 3505 3506 for (int i = 0; i < xrc.size(); i++) { 3507 GuiUtils.processXmlMenuBar(xrc.getRoot(i), menuBar, getIdv(), menuMap, actionIcons); 3508 } 3509 3510 menuIds = new Hashtable<>(menuMap); 3511 3512 // Ensure that the "help" menu is the last menu. 3513 JMenuItem helpMenu = menuMap.get(MENU_HELP); 3514 if (helpMenu != null) { 3515 menuBar.remove(helpMenu); 3516 menuBar.add(helpMenu); 3517 } 3518 3519 //TODO: Perhaps we will put the different skins in the menu? 3520 JMenu newDisplayMenu = (JMenu)menuMap.get(MENU_NEWDISPLAY); 3521 if (newDisplayMenu != null) { 3522 MenuUtil.makeMenu(newDisplayMenu, makeSkinMenuItems(makeMenuBarActionListener(), true, false)); 3523 } 3524 3525// final JMenu publishMenu = menuMap.get(MENU_PUBLISH); 3526// if (publishMenu != null) { 3527// if (!getPublishManager().isPublishingEnabled()) 3528// publishMenu.getParent().remove(publishMenu); 3529// else 3530// getPublishManager().initMenu(publishMenu); 3531// } 3532 3533 for (Entry<String, JMenuItem> e : menuMap.entrySet()) { 3534 if (!(e.getValue() instanceof JMenu)) { 3535 continue; 3536 } 3537 String menuId = e.getKey(); 3538 JMenu menu = (JMenu)e.getValue(); 3539 menu.addMenuListener(makeMenuBarListener(menuId, menu, idvWindow)); 3540 } 3541 return menuBar; 3542 } 3543 3544 private final ActionListener makeMenuBarActionListener() { 3545 final IdvResourceManager mngr = getResourceManager(); 3546 return ae -> { 3547 XmlResourceCollection skins = mngr.getXmlResources(mngr.RSC_SKIN); 3548 int skinIndex = ((Integer)ae.getSource()).intValue(); 3549 createNewWindow(null, true, getWindowTitleFromSkin(skinIndex), 3550 skins.get(skinIndex).toString(), 3551 skins.getRoot(skinIndex, false), true, null); 3552 }; 3553 } 3554 3555 private final MenuListener makeMenuBarListener(final String id, final JMenu menu, final IdvWindow idvWindow) { 3556 return new MenuListener() { 3557 public void menuCanceled(final MenuEvent e) { } 3558 public void menuDeselected(final MenuEvent e) { handleMenuDeSelected(id, menu, idvWindow); } 3559 public void menuSelected(final MenuEvent e) { handleMenuSelected(id, menu, idvWindow); } 3560 }; 3561 } 3562 3563 /** 3564 * Handle mouse clicks that occur within the toolbar. 3565 */ 3566 private class PopupListener extends MouseAdapter { 3567 3568 private JPopupMenu popup; 3569 3570 public PopupListener(JPopupMenu p) { 3571 popup = p; 3572 } 3573 3574 // handle right clicks on os x and linux 3575 public void mousePressed(MouseEvent e) { 3576 if (e.isPopupTrigger()) { 3577 popup.show(e.getComponent(), e.getX(), e.getY()); 3578 } 3579 } 3580 3581 // Windows doesn't seem to trigger mousePressed() for right clicks, but 3582 // never fear; mouseReleased() does the job. 3583 public void mouseReleased(MouseEvent e) { 3584 if (e.isPopupTrigger()) { 3585 popup.show(e.getComponent(), e.getX(), e.getY()); 3586 } 3587 } 3588 } 3589 3590 /** 3591 * Handle (polymorphically) the {@link ucar.unidata.idv.ui.DataControlDialog}. 3592 * This dialog is used to either select a display control to create 3593 * or is used to set the timers used for a {@link ucar.unidata.data.DataSource}. 3594 * 3595 * @param dcd The dialog 3596 */ 3597 public void processDialog(DataControlDialog dcd) { 3598 int estimatedMB = getEstimatedMegabytes(dcd); 3599 if (estimatedMB > 0) { 3600 double totalMem = Runtime.getRuntime().maxMemory(); 3601 double highMem = Runtime.getRuntime().totalMemory(); 3602 double freeMem = Runtime.getRuntime().freeMemory(); 3603 double usedMem = (highMem - freeMem); 3604 int availableMB = Math.round( ((float)totalMem - (float)usedMem) / 1024f / 1024f); 3605 int percentOfAvailable = Math.round((float)estimatedMB / (float)availableMB * 100f); 3606 if (percentOfAvailable > 95) { 3607 String message = "<html>You are attempting to load " + estimatedMB + "MB of data,<br>"; 3608 message += "which exceeds 95% of total amount available (" + availableMB +"MB).<br>"; 3609 message += "Data load cancelled.</html>"; 3610 JComponent msgLabel = new JLabel(message); 3611 GuiUtils.showDialog("Data Size", msgLabel); 3612 return; 3613 } else if (percentOfAvailable >= 75) { 3614 String message = "<html>You are attempting to load " + estimatedMB + "MB of data,<br>"; 3615 message += percentOfAvailable + "% of the total amount available (" + availableMB + "MB).<br>"; 3616 message += "Continue loading data?</html>"; 3617 JComponent msgLabel = new JLabel(message); 3618 if (!GuiUtils.askOkCancel("Data Size", msgLabel)) { 3619 return; 3620 } 3621 } 3622 } 3623 super.processDialog(dcd); 3624 } 3625 3626 /** 3627 * Estimate the number of megabytes that will be used by this data selection 3628 * 3629 * @param dcd Data control dialog containing the data selection whose size 3630 * we'd like to estimate. Cannot be {@code null}. 3631 * 3632 * @return Rough estimate of the size (in megabytes) of the 3633 * {@code DataSelection} within {@code dcd}. 3634 */ 3635 protected int getEstimatedMegabytes(DataControlDialog dcd) { 3636 int estimatedMB = 0; 3637 DataChoice dataChoice = dcd.getDataChoice(); 3638 if (dataChoice != null) { 3639 Object[] selectedControls = dcd.getSelectedControls(); 3640 for (int i = 0; i < selectedControls.length; i++) { 3641 ControlDescriptor cd = (ControlDescriptor) selectedControls[i]; 3642 3643 //Check if the data selection is ok 3644 if(!dcd.getDataSelectionWidget().okToCreateTheDisplay(cd.doesLevels())) { 3645 continue; 3646 } 3647 3648 DataSelection dataSelection = dcd.getDataSelectionWidget().createDataSelection(cd.doesLevels()); 3649 3650 // Get the size in pixels of the requested image 3651 Object gotSize = dataSelection.getProperty("SIZE"); 3652 if (gotSize == null) { 3653 continue; 3654 } 3655 List<String> dims = StringUtil.split(gotSize, " ", false, false); 3656 int myLines = -1; 3657 int myElements = -1; 3658 if (dims.size() == 2) { 3659 try { 3660 myLines = Integer.parseInt(dims.get(0)); 3661 myElements = Integer.parseInt(dims.get(1)); 3662 } 3663 catch (Exception e) { } 3664 } 3665 3666 // Get the count of times requested 3667 int timeCount = 1; 3668 DataSelectionWidget dsw = dcd.getDataSelectionWidget(); 3669 List times = dsw.getSelectedDateTimes(); 3670 List timesAll = dsw.getAllDateTimes(); 3671 if ((times != null) && !times.isEmpty()) { 3672 timeCount = times.size(); 3673 } else if ((timesAll != null) && !timesAll.isEmpty()) { 3674 timeCount = timesAll.size(); 3675 } 3676 3677 // Total number of pixels 3678 // Assumed lines x elements x times x 4bytes 3679 // Empirically seems to be taking *twice* that (64bit fields??) 3680 float totalPixels = (float)myLines * (float)myElements * (float)timeCount; 3681 float totalBytes = totalPixels * 4 * 2; 3682 estimatedMB += Math.round(totalBytes / 1024f / 1024f); 3683 3684 int additionalMB = 0; 3685 // Empirical tests show that textures are not affecting 3686 // required memory... comment out for now 3687 /* 3688 int textureDimensions = 2048; 3689 int mbPerTexture = Math.round((float)textureDimensions * (float)textureDimensions * 4 / 1024f / 1024f); 3690 int textureCount = (int)Math.ceil((float)myLines / 2048f) * (int)Math.ceil((float)myElements / 2048f); 3691 int additionalMB = textureCount * mbPerTexture * timeCount; 3692 */ 3693 estimatedMB += additionalMB; 3694 } 3695 } 3696 return estimatedMB; 3697 } 3698 3699 /** 3700 * Represents a SavedBundle as a tree. 3701 */ 3702 private class BundleTreeNode { 3703 3704 private String name; 3705 3706 private SavedBundle bundle; 3707 3708 private List<BundleTreeNode> kids; 3709 3710 /** 3711 * This constructor is used to build a node that is considered a 3712 * {@literal "parent"}. 3713 * 3714 * These nodes only have child nodes, no {@code SavedBundles}. This 3715 * was done so that distinguishing between bundles and bundle 3716 * subcategories would be easy. 3717 * 3718 * @param name The name of this node. For a parent node with 3719 * {@literal "Toolbar>cat"} as the path, the name parameter would 3720 * contain only {@literal "cat"}. 3721 */ 3722 public BundleTreeNode(String name) { 3723 this(name, null); 3724 } 3725 3726 /** 3727 * Nodes constructed using this constructor can only ever be child 3728 * nodes. 3729 * 3730 * @param name The name of the SavedBundle. 3731 * @param bundle A reference to the SavedBundle. 3732 */ 3733 public BundleTreeNode(String name, SavedBundle bundle) { 3734 this.name = name; 3735 this.bundle = bundle; 3736 kids = new LinkedList<>(); 3737 } 3738 3739 /** 3740 * @param child The node to be added to the current node. 3741 */ 3742 public void addChild(BundleTreeNode child) { 3743 kids.add(child); 3744 } 3745 3746 /** 3747 * @return Returns all child nodes of this node. 3748 */ 3749 public List<BundleTreeNode> getChildren() { 3750 return kids; 3751 } 3752 3753 /** 3754 * @return Return the SavedBundle associated with this node (if any). 3755 */ 3756 public SavedBundle getBundle() { 3757 return bundle; 3758 } 3759 3760 /** 3761 * @return The name of this node. 3762 */ 3763 public String getName() { 3764 return name; 3765 } 3766 } 3767 3768 /** 3769 * A type of {@code HttpFormEntry} that supports line wrapping for 3770 * text area entries. 3771 * 3772 * @see HttpFormEntry 3773 */ 3774 private static class FormEntry extends HttpFormEntry { 3775 /** Initial contents of this entry. */ 3776 private String value = ""; 3777 3778 /** Whether or not the JTextArea should wrap lines. */ 3779 private boolean wrap = true; 3780 3781 /** Entry type. Used to remain compatible with the IDV. */ 3782 private int type = HttpFormEntry.TYPE_AREA; 3783 3784 /** Number of rows in the JTextArea. */ 3785 private int rows = 5; 3786 3787 /** Number of columns in the JTextArea. */ 3788 private int cols = 30; 3789 3790 /** GUI representation of this entry. */ 3791 private JTextArea component = new JTextArea(value, rows, cols); 3792 3793 /** 3794 * Required to keep Java happy. 3795 */ 3796 public FormEntry() { 3797 super(HttpFormEntry.TYPE_AREA, "form_data[description]", 3798 "Description:"); 3799 } 3800 3801 /** 3802 * Using this constructor allows McIDAS-V to control whether or not a 3803 * HttpFormEntry performs line wrapping for JTextArea components. 3804 * 3805 * @param wrap Whether or not line wrapping should be enabled. 3806 * @param type Type of this entry 3807 * @param name Name 3808 * @param label Label 3809 * @param value Initial value 3810 * @param rows Number of rows. 3811 * @param cols Number of columns. 3812 * @param required Whether or not the entry will be required. 3813 */ 3814 public FormEntry(boolean wrap, int type, String name, String label, String value, int rows, int cols, boolean required) { 3815 super(type, name, label, value, rows, cols, required); 3816 this.type = type; 3817 this.rows = rows; 3818 this.cols = cols; 3819 this.wrap = wrap; 3820 } 3821 3822 /** 3823 * Overrides the IDV method so that the McIDAS-V support request form 3824 * will wrap lines in the "Description" field. 3825 * 3826 * @param guiComps List to which this instance should be added. 3827 */ 3828 @SuppressWarnings("unchecked") 3829 @Override public void addToGui(List guiComps) { 3830 if (type == HttpFormEntry.TYPE_AREA) { 3831 Dimension minSize = new Dimension(500, 200); 3832 guiComps.add(LayoutUtil.top(GuiUtils.rLabel(getLabel()))); 3833 component.setLineWrap(wrap); 3834 component.setWrapStyleWord(wrap); 3835 JScrollPane sp = new JScrollPane(component); 3836 sp.setPreferredSize(minSize); 3837 sp.setMinimumSize(minSize); 3838 guiComps.add(sp); 3839 } else { 3840 super.addToGui(guiComps); 3841 } 3842 } 3843 3844 /** 3845 * Since the IDV doesn't provide a getComponent for 3846 * {@code addToGui}, we must make our {@code component} field 3847 * local to this class. 3848 * Hijacks any value requests so that the local {@code component} 3849 * field is queried, not the IDV's. 3850 * 3851 * @return Contents of form. 3852 */ 3853 @Override public String getValue() { 3854 if (type != HttpFormEntry.TYPE_AREA) { 3855 return super.getValue(); 3856 } 3857 return component.getText(); 3858 } 3859 3860 /** 3861 * Hijacks any requests to set the {@code component} field's text. 3862 */ 3863 @Override public void setValue(final String newValue) { 3864 if (type == HttpFormEntry.TYPE_AREA) { 3865 component.setText(newValue); 3866 } else { 3867 super.setValue(newValue); 3868 } 3869 } 3870 } 3871 3872 /** 3873 * A {@code ToolbarStyle} is a representation of the way icons associated 3874 * with current toolbar actions should be displayed. This notion is so far 3875 * limited to the sizing of icons, but that may change. 3876 */ 3877 public enum ToolbarStyle { 3878 /** 3879 * Represents the current toolbar actions as large icons. Currently, 3880 * {@literal "large"} is defined as {@code 32 x 32} pixels. 3881 */ 3882 LARGE("Large Icons", "action.icons.large", 32), 3883 3884 /** 3885 * Represents the current toolbar actions as medium icons. Currently, 3886 * {@literal "medium"} is defined as {@code 22 x 22} pixels. 3887 */ 3888 MEDIUM("Medium Icons", "action.icons.medium", 22), 3889 3890 /** 3891 * Represents the current toolbar actions as small icons. Currently, 3892 * {@literal "small"} is defined as {@code 16 x 16} pixels. 3893 */ 3894 SMALL("Small Icons", "action.icons.small", 16); 3895 3896 /** Label to use in the toolbar customization popup menu. */ 3897 private final String label; 3898 3899 /** Signals that the user selected a specific icon size. */ 3900 private final String action; 3901 3902 /** Icon dimensions. Each icon should be {@code size * size}. */ 3903 private final int size; 3904 3905 /** 3906 * {@link #size} in {@link String} form, merely for use with the IDV's 3907 * preference functionality. 3908 */ 3909 private final String sizeAsString; 3910 3911 /** 3912 * Initializes a toolbar style. 3913 * 3914 * @param label Label used in the toolbar popup menu. 3915 * @param action Command that signals the user selected this toolbar 3916 * style. 3917 * @param size Dimensions of the icons. 3918 * 3919 * @throws NullPointerException if {@code label} or {@code action} are 3920 * null. 3921 * 3922 * @throws IllegalArgumentException if {@code size} is not positive. 3923 */ 3924 ToolbarStyle(final String label, final String action, final int size) { 3925 requireNonNull(label, "Label cannot be null."); 3926 requireNonNull(action, "Action cannot be null."); 3927 3928 if (size <= 0) { 3929 throw new IllegalArgumentException("Size must be a positive integer"); 3930 } 3931 3932 this.label = label; 3933 this.action = action; 3934 this.size = size; 3935 this.sizeAsString = Integer.toString(size); 3936 } 3937 3938 /** 3939 * Returns the label to use as a brief description of this style. 3940 * 3941 * @return Description of style (suitable for a label). 3942 */ 3943 public String getLabel() { 3944 return label; 3945 } 3946 3947 /** 3948 * Returns the action command associated with this style. 3949 * 3950 * @return This style's {@literal "action command"}. 3951 */ 3952 public String getAction() { 3953 return action; 3954 } 3955 3956 /** 3957 * Returns the dimensions of icons used in this style. 3958 * 3959 * @return Dimensions of this style's icons. 3960 */ 3961 public int getSize() { 3962 return size; 3963 } 3964 3965 /** 3966 * Returns {@link #size} as a {@link String} to make cooperating with 3967 * the IDV preferences code easier. 3968 * 3969 * @return String representation of this style's icon dimensions. 3970 */ 3971 public String getSizeAsString() { 3972 return sizeAsString; 3973 } 3974 3975 /** 3976 * Returns a brief description of this {@code ToolbarStyle}. 3977 * 3978 * <p>A typical example: 3979 * {@code [ToolbarStyle@1337: label="Large Icons", size=32]} 3980 * 3981 * <p>Note that the format and details provided are subject to change. 3982 * 3983 * @return String representation of this {@code ToolbarStyle} instance. 3984 */ 3985 public String toString() { 3986 return String.format("[ToolbarStyle@%x: label=%s, size=%d]", 3987 hashCode(), label, size); 3988 } 3989 3990 /** 3991 * Convenience method for build the toolbar customization popup menu. 3992 * 3993 * @param manager {@link UIManager} that will be listening for action 3994 * commands. 3995 * 3996 * @return Menu item that has {@code manager} listening for 3997 * {@link #action}. 3998 */ 3999 protected JMenuItem buildMenuItem(final UIManager manager) { 4000 JMenuItem item = new JRadioButtonMenuItem(label); 4001 item.setActionCommand(action); 4002 item.addActionListener(manager); 4003 return item; 4004 } 4005 } 4006 4007 /** 4008 * Represents what McIDAS-V {@literal "knows"} about IDV actions. 4009 */ 4010 protected enum ActionAttribute { 4011 4012 /** 4013 * Unique identifier for an IDV action. Required attribute. 4014 * 4015 * @see IdvUIManager#ATTR_ID 4016 */ 4017 ID(ATTR_ID), 4018 4019 /** 4020 * Path to an icon for this action. Currently required. Note that 4021 * McIDAS-V differs from the IDV in that actions must support different 4022 * icon sizes. This is implemented in McIDAS-V by simply having the value 4023 * of this path be a valid {@literal "format string"}, 4024 * such as {@code image="/edu/wisc/ssec/mcidasv/resources/icons/toolbar/background-image%d.png"} 4025 * 4026 * <p>The upshot is that this value <b>will not be a valid path in 4027 * McIDAS-V</b>. Use either {@link IdvAction#getMenuIcon()} or 4028 * {@link IdvAction#getIconForStyle}. 4029 * 4030 * @see IdvUIManager#ATTR_IMAGE 4031 * @see IdvAction#getRawIconPath() 4032 * @see IdvAction#getMenuIcon() 4033 * @see IdvAction#getIconForStyle 4034 */ 4035 ICON(ATTR_IMAGE), 4036 4037 /** 4038 * Brief description of a IDV action. Required attribute. 4039 * @see IdvUIManager#ATTR_DESCRIPTION 4040 */ 4041 DESCRIPTION(ATTR_DESCRIPTION), 4042 4043 /** 4044 * Allows actions to be clustered into arbitrary groups. Currently 4045 * optional; defaults to {@literal "General"}. 4046 * @see IdvUIManager#ATTR_GROUP 4047 */ 4048 GROUP(ATTR_GROUP, "General"), 4049 4050 /** 4051 * Actual method call used to invoke a given IDV action. Required 4052 * attribute. 4053 * @see IdvUIManager#ATTR_ACTION 4054 */ 4055 ACTION(ATTR_ACTION); 4056 4057 /** 4058 * A blank {@link String} if this is a required attribute, or a 4059 * {@code String} value to use in case this attribute has not been 4060 * specified by a given IDV action. 4061 */ 4062 private final String defaultValue; 4063 4064 /** 4065 * String representation of this attribute as used by the IDV. 4066 * @see #asIdvString() 4067 */ 4068 private final String idvString; 4069 4070 /** Whether or not this attribute is required. */ 4071 private final boolean required; 4072 4073 /** 4074 * Creates a constant that represents a required IDV action attribute. 4075 * 4076 * @param idvString Corresponding IDV attribute {@link String}. Cannot be {@code null}. 4077 * 4078 * @throws NullPointerException if {@code idvString} is {@code null}. 4079 */ 4080 ActionAttribute(final String idvString) { 4081 requireNonNull(idvString, "Cannot be associated with a null IDV action attribute String"); 4082 4083 this.idvString = idvString; 4084 this.defaultValue = ""; 4085 this.required = true; 4086 } 4087 4088 /** 4089 * Creates a constant that represents an optional IDV action attribute. 4090 * 4091 * @param idvString Corresponding IDV attribute {@link String}. 4092 * Cannot be {@code null}. 4093 * @param defValue Default value for actions that do not have this 4094 * attribute. Cannot be {@code null} or an empty {@code String}. 4095 * 4096 * @throws NullPointerException if either {@code idvString} or 4097 * {@code defValue} is {@code null}. 4098 * @throws IllegalArgumentException if {@code defValue} is an empty 4099 * {@code String}. 4100 * 4101 */ 4102 ActionAttribute(final String idvString, final String defValue) { 4103 requireNonNull(idvString, "Cannot be associated with a null IDV action attribute String"); 4104 Contract.notNull(defValue, "Optional action attribute \"%s\" requires a non-null default value", toString()); 4105 Contract.checkArg(!defValue.equals(""), "Optional action attribute \"%s\" requires something more descriptive than an empty String", toString()); 4106 4107 this.idvString = idvString; 4108 this.defaultValue = defValue; 4109 this.required = (defaultValue.equals("")); 4110 } 4111 4112 /** 4113 * @return The {@link String} representation of this attribute, as is 4114 * used by the IDV. 4115 * 4116 * @see IdvUIManager#ATTR_ACTION 4117 * @see IdvUIManager#ATTR_DESCRIPTION 4118 * @see IdvUIManager#ATTR_GROUP 4119 * @see IdvUIManager#ATTR_ID 4120 * @see IdvUIManager#ATTR_IMAGE 4121 */ 4122 public String asIdvString() { return idvString; } 4123 4124 /** 4125 * @return {@literal "Default value"} for this attribute. 4126 * Blank {@link String}s imply that the attribute is required (and 4127 * thus lacks a true default value). 4128 */ 4129 public String defaultValue() { return defaultValue; } 4130 4131 /** 4132 * @return Whether or not this attribute is a required attribute for 4133 * valid {@link IdvAction}s. 4134 */ 4135 public boolean isRequired() { return required; } 4136 } 4137 4138 /** 4139 * Represents the set of known {@link IdvAction IdvActions} in an idiom 4140 * that can be easily used by both the IDV and McIDAS-V. 4141 */ 4142 // TODO(jon:101): use Sets instead of maps and whatnot 4143 // TODO(jon:103): create an invalid IdvAction 4144 public static final class IdvActions { 4145 4146 /** Maps {@literal "id"} values to {@link IdvAction IdvActions}. */ 4147 private final Map<String, IdvAction> idToAction = 4148 new ConcurrentHashMap<>(); 4149 4150 /** 4151 * Collects {@link IdvAction IdvActions} {@literal "under"} common 4152 * group values. 4153 */ 4154 // TODO(jon:102): this should probably become concurrency-friendly. 4155 private final Map<String, Set<IdvAction>> groupToActions = 4156 new LinkedHashMap<>(); 4157 4158 /** 4159 * Creates an object that represents the application's 4160 * {@link IdvAction IdvActions}. 4161 * 4162 * @param idv Reference to the IDV {@literal "god"} object. 4163 * Cannot be {@code null}. 4164 * @param collectionId IDV resource collection that contains our 4165 * actions. Cannot be {@code null}. 4166 * 4167 * @throws NullPointerException if {@code idv} or {@code collectionId} 4168 * is {@code null}. 4169 */ 4170 public IdvActions(final IntegratedDataViewer idv, final XmlIdvResource collectionId) { 4171 requireNonNull(idv, "Cannot provide a null IDV reference"); 4172 requireNonNull(collectionId, "Cannot build actions from a null collection id"); 4173 4174 // TODO(jon): benchmark use of xpath 4175 String query = "//action[@id and @image and @description and @action]"; 4176 for (Element e : elements(idv, collectionId, query)) { 4177 IdvAction a = new IdvAction(e); 4178 String id = a.getAttribute(ActionAttribute.ID); 4179 idToAction.put(id, a); 4180 String group = a.getAttribute(ActionAttribute.GROUP); 4181 if (!groupToActions.containsKey(group)) { 4182 groupToActions.put(group, new LinkedHashSet<>()); 4183 } 4184 Set<IdvAction> groupedIds = groupToActions.get(group); 4185 groupedIds.add(a); 4186 } 4187 } 4188 4189 /** 4190 * Attempts to return the {@link IdvAction} associated with the given 4191 * {@code actionId}. 4192 * 4193 * @param actionId Identifier to use in the search. Cannot be 4194 * {@code null}. 4195 * 4196 * @return Either the {@code IdvAction} that matches {@code actionId} 4197 * or {@code null} if there was no match. 4198 * 4199 * @throws NullPointerException if {@code actionId} is {@code null}. 4200 */ 4201 // TODO(jon:103) here 4202 public IdvAction getAction(final String actionId) { 4203 requireNonNull(actionId, "Null action identifiers are not allowed"); 4204 return idToAction.get(actionId); 4205 } 4206 4207 /** 4208 * Searches for the action associated with {@code actionId} and 4209 * returns the value associated with the given {@link ActionAttribute}. 4210 * 4211 * @param actionId Identifier to search for. Cannot be {@code null}. 4212 * @param attr Attribute whose value is desired. Cannot be {@code null}. 4213 * 4214 * @return Either the desired attribute value of the desired action, 4215 * or {@code null} if {@code actionId} has no associated action. 4216 * 4217 * @throws NullPointerException if either {@code actionId} or 4218 * {@code attr} is {@code null}. 4219 */ 4220 // TODO(jon:103) here 4221 public String getAttributeForAction(final String actionId, final ActionAttribute attr) { 4222 requireNonNull(actionId, "Null action identifiers are not allowed"); 4223 requireNonNull(attr, "Actions cannot have values associated with a null attribute"); 4224 IdvAction action = idToAction.get(actionId); 4225 if (action == null) { 4226 return null; 4227 } 4228 return action.getAttribute(attr); 4229 } 4230 4231 /** 4232 * Attempts to return the XML {@link Element} that 4233 * {@literal "represents"} the action associated with {@code actionId}. 4234 * 4235 * @param actionId Identifier whose XML element is desired. 4236 * Cannot be {@code null}. 4237 * 4238 * @return Either the XML element associated with {@code actionId} or 4239 * {@code null}. 4240 * 4241 * @throws NullPointerException if {@code actionId} is {@code null}. 4242 * 4243 * @see IdvAction#originalElement 4244 */ 4245 // TODO(jon:103) here 4246 public Element getElementForAction(final String actionId) { 4247 requireNonNull(actionId, "Cannot search for a null action identifier"); 4248 IdvAction action = idToAction.get(actionId); 4249 if (action == null) { 4250 return null; 4251 } 4252 return action.getElement(); 4253 } 4254 4255 /** 4256 * Attempts to return an {@link Icon} for a given {@link ActionAttribute#ID} and 4257 * {@link ToolbarStyle}. 4258 * 4259 * @param actionId ID of the action whose {@literal "styled"} icon is 4260 * desired. Cannot be {@code null}. 4261 * @param style Desired {@code Icon} style. Cannot be {@code null}. 4262 * 4263 * @return Either the {@code Icon} associated with {@code actionId} 4264 * and {@code style}, or {@code null}. 4265 * 4266 * @throws NullPointerException if either {@code actionId} or 4267 * {@code style} is {@code null}. 4268 */ 4269 // TODO(jon:103) here 4270 public Icon getStyledIconFor(final String actionId, final ToolbarStyle style) { 4271 requireNonNull(actionId, "Cannot get an icon for a null action identifier"); 4272 requireNonNull(style, "Cannot get an icon for a null ToolbarStyle"); 4273 IdvAction a = idToAction.get(actionId); 4274 if (a == null) { 4275 return null; 4276 } 4277 return a.getIconForStyle(style); 4278 } 4279 4280 // TODO(jon:105): replace with something better 4281 public List<String> getAttributes(final ActionAttribute attr) { 4282 requireNonNull(attr, "Actions cannot have null attributes"); 4283 List<String> attributeList = arrList(idToAction.size()); 4284 for (Map.Entry<String, IdvAction> entry : idToAction.entrySet()) { 4285 attributeList.add(entry.getValue().getAttribute(attr)); 4286 } 4287 return attributeList; 4288 } 4289 4290 /** 4291 * @return List of all known {@code IdvAction}s. 4292 */ 4293 public List<IdvAction> getAllActions() { 4294 return arrList(idToAction.values()); 4295 } 4296 4297 /** 4298 * @return List of all known action groupings. 4299 * 4300 * @see ActionAttribute#GROUP 4301 * @see #getActionsForGroup(String) 4302 */ 4303 public List<String> getAllGroups() { 4304 return arrList(groupToActions.keySet()); 4305 } 4306 4307 /** 4308 * Returns the {@link Set} of {@link IdvAction}s associated with the 4309 * given {@code group}. 4310 * 4311 * @param group Group whose associated actions you want. Cannot be 4312 * {@code null}. 4313 * 4314 * @return Collection of {@code IdvAction}s associated with 4315 * {@code group}. A blank collection is returned if there are no actions 4316 * associated with {@code group}. 4317 * 4318 * @throws NullPointerException if {@code group} is {@code null}. 4319 * 4320 * @see ActionAttribute#GROUP 4321 * @see #getAllGroups() 4322 */ 4323 public Set<IdvAction> getActionsForGroup(final String group) { 4324 requireNonNull(group, "Actions cannot be associated with a null group"); 4325 if (!groupToActions.containsKey(group)) { 4326 return Collections.emptySet(); 4327 } 4328 return groupToActions.get(group); 4329 } 4330 4331 /** 4332 * Returns a summary of the known IDV actions. Please note that this 4333 * format is subject to change, and is not intended for serialization. 4334 * 4335 * @return String that looks like 4336 * {@code [IdvActions@HASHCODE: actions=...]}. 4337 */ 4338 @Override public String toString() { 4339 return String.format("[IdvActions@%x: actions=%s]", hashCode(), idToAction); 4340 } 4341 } 4342 4343 /** 4344 * Represents an individual IDV action. Should be fairly adaptable to 4345 * unforeseen changes from Unidata? 4346 */ 4347 // TODO(jon:106): Implement equals/hashCode so that you can use these in Sets. The only relevant value should be the id, right? 4348 public static final class IdvAction { 4349 4350 /** The XML {@link Element} that represents this IDV action. */ 4351 private final Element originalElement; 4352 4353 /** Mapping of (known) XML attributes to values for this individual action. */ 4354 private final Map<ActionAttribute, String> attributes; 4355 4356 /** 4357 * Simple {@literal "cache"} for the different icons this action has 4358 * displayed. This is {@literal "lazy"}, so the cache does not contain 4359 * icons for {@link ToolbarStyle}s that haven't been used. 4360 */ 4361 private final Map<ToolbarStyle, Icon> iconCache = 4362 new ConcurrentHashMap<>(); 4363 4364 /** 4365 * Creates a representation of an IDV action using a given 4366 * {@link Element}. 4367 * 4368 * @param element XML representation of an IDV action. 4369 * Cannot be {@code null}. 4370 * 4371 * @throws NullPointerException if {@code element} is {@code null}. 4372 * @throws IllegalArgumentException if {@code element} is not a valid 4373 * IDV action. 4374 * 4375 * @see UIManager#isValidIdvAction(Element) 4376 */ 4377 public IdvAction(final Element element) { 4378 requireNonNull(element, "Cannot build an action from a null element"); 4379 // TODO(jon:107): need a way to diagnose what's wrong with the action? 4380 Contract.checkArg(isValidIdvAction(element), "Action lacks required attributes"); 4381 originalElement = element; 4382 attributes = actionElementToMap(element); 4383 } 4384 4385 /** 4386 * @return Returns the {@literal "raw"} path to the icon associated 4387 * with this action. Remember that this is actually a 4388 * {@literal "format string"} and should not be considered a valid path! 4389 * 4390 * @see #getIconForStyle 4391 */ 4392 public String getRawIconPath() { 4393 return attributes.get(ActionAttribute.ICON); 4394 } 4395 4396 /** 4397 * @return Returns the {@link Icon} associated with 4398 * {@link ToolbarStyle#SMALL}. 4399 */ 4400 public Icon getMenuIcon() { 4401 return getIconForStyle(ToolbarStyle.SMALL); 4402 } 4403 4404 /** 4405 * Returns the {@link Icon} associated with this action and the given 4406 * {@link ToolbarStyle}. 4407 * 4408 * @param style {@literal "Style"} of the {@code Icon} to be returned. 4409 * Cannot be {@code null}. 4410 * 4411 * @return This action's {@code Icon} with {@code style} 4412 * {@literal "applied."} 4413 * 4414 * @see ActionAttribute#ICON 4415 * @see #iconCache 4416 */ 4417 public Icon getIconForStyle(final ToolbarStyle style) { 4418 requireNonNull(style, "Cannot build an icon for a null ToolbarStyle"); 4419 if (!iconCache.containsKey(style)) { 4420 String styledPath = String.format(getRawIconPath(), style.getSize()); 4421 URL tmp = getClass().getResource(styledPath); 4422 iconCache.put(style, new ImageIcon(Toolkit.getDefaultToolkit().getImage(tmp))); 4423 } 4424 return iconCache.get(style); 4425 } 4426 4427 /** 4428 * @return Returns the identifier of this {@code IdvAction}. 4429 */ 4430 public String getId() { 4431 return getAttribute(ActionAttribute.ID); 4432 } 4433 4434 /** 4435 * Representation of this {@code IdvAction} as an 4436 * {@literal "IDV action call"}. 4437 * 4438 * @return String that is suitable to hand off to the IDV for execution. 4439 */ 4440 public String getCommand() { 4441 return "idv.handleAction('action:"+getAttribute(ActionAttribute.ID)+"')"; 4442 } 4443 4444 /** 4445 * Returns the value associated with a given {@link ActionAttribute} 4446 * for this action. 4447 * 4448 * @param attr ActionAttribute whose value you want. 4449 * Cannot be {@code null}. 4450 * 4451 * @return Value associated with {@code attr}. 4452 * 4453 * @throws NullPointerException if {@code attr} is {@code null}. 4454 */ 4455 public String getAttribute(final ActionAttribute attr) { 4456 requireNonNull(attr, "No values can be associated with a null ActionAttribute"); 4457 return attributes.get(attr); 4458 } 4459 4460 /** 4461 * @return The XML {@link Element} used to create this {@code IdvAction}. 4462 */ 4463 // TODO(jon:104): any way to copy this element? if so, this can become an immutable class! 4464 public Element getElement() { 4465 return originalElement; 4466 } 4467 4468 /** 4469 * Returns a brief description of this action. Please note that the 4470 * format is subject to change and is not intended for serialization. 4471 * 4472 * @return String that looks like 4473 * {@code [IdvAction@HASHCODE: attributes=...]}. 4474 */ 4475 @Override public String toString() { 4476 return String.format("[IdvAction@%x: attributes=%s]", hashCode(), attributes); 4477 } 4478 } 4479}