001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2015 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.event.TreeSelectionEvent; 107import javax.swing.event.TreeSelectionListener; 108import javax.swing.text.JTextComponent; 109import javax.swing.tree.DefaultMutableTreeNode; 110import javax.swing.tree.DefaultTreeCellRenderer; 111import javax.swing.tree.DefaultTreeModel; 112import javax.swing.tree.TreeSelectionModel; 113 114import org.slf4j.Logger; 115import org.slf4j.LoggerFactory; 116import org.w3c.dom.Document; 117import org.w3c.dom.Element; 118import org.w3c.dom.NodeList; 119 120import ucar.unidata.data.DataChoice; 121import ucar.unidata.data.DataOperand; 122import ucar.unidata.data.DataSelection; 123import ucar.unidata.data.DataSource; 124import ucar.unidata.data.DataSourceImpl; 125import ucar.unidata.data.DerivedDataChoice; 126import ucar.unidata.data.UserOperandValue; 127import ucar.unidata.geoloc.LatLonPointImpl; 128import ucar.unidata.idv.ControlDescriptor; 129import ucar.unidata.idv.IdvPersistenceManager; 130import ucar.unidata.idv.IdvPreferenceManager; 131import ucar.unidata.idv.IdvResourceManager; 132import ucar.unidata.idv.IntegratedDataViewer; 133import ucar.unidata.idv.SavedBundle; 134import ucar.unidata.idv.ViewManager; 135import ucar.unidata.idv.ViewState; 136import ucar.unidata.idv.IdvResourceManager.XmlIdvResource; 137import ucar.unidata.idv.control.DisplayControlImpl; 138import ucar.unidata.idv.ui.DataControlDialog; 139import ucar.unidata.idv.ui.DataSelectionWidget; 140import ucar.unidata.idv.ui.DataSelector; 141import ucar.unidata.idv.ui.IdvComponentGroup; 142import ucar.unidata.idv.ui.IdvComponentHolder; 143import ucar.unidata.idv.ui.IdvUIManager; 144import ucar.unidata.idv.ui.IdvWindow; 145import ucar.unidata.idv.ui.IdvXmlUi; 146import ucar.unidata.idv.ui.ViewPanel; 147import ucar.unidata.idv.ui.WindowInfo; 148import ucar.unidata.metdata.NamedStationTable; 149import ucar.unidata.ui.ComponentGroup; 150import ucar.unidata.ui.ComponentHolder; 151import ucar.unidata.ui.HttpFormEntry; 152import ucar.unidata.ui.LatLonWidget; 153import ucar.unidata.ui.RovingProgress; 154import ucar.unidata.ui.XmlUi; 155import ucar.unidata.util.GuiUtils; 156import ucar.unidata.util.IOUtil; 157import ucar.unidata.util.LayoutUtil; 158import ucar.unidata.util.LogUtil; 159import ucar.unidata.util.MenuUtil; 160import ucar.unidata.util.Misc; 161import ucar.unidata.util.Msg; 162import ucar.unidata.util.ObjectListener; 163import ucar.unidata.util.PatternFileFilter; 164import ucar.unidata.util.StringUtil; 165import ucar.unidata.util.TwoFacedObject; 166import ucar.unidata.xml.XmlResourceCollection; 167import ucar.unidata.xml.XmlUtil; 168import edu.wisc.ssec.mcidasv.Constants; 169import edu.wisc.ssec.mcidasv.McIDASV; 170import edu.wisc.ssec.mcidasv.PersistenceManager; 171import edu.wisc.ssec.mcidasv.StateManager; 172import edu.wisc.ssec.mcidasv.supportform.McvStateCollector; 173import edu.wisc.ssec.mcidasv.supportform.SupportForm; 174import edu.wisc.ssec.mcidasv.util.CollectionHelpers; 175import edu.wisc.ssec.mcidasv.util.Contract; 176import edu.wisc.ssec.mcidasv.util.McVGuiUtils; 177import edu.wisc.ssec.mcidasv.util.MemoryMonitor; 178 179/** 180 * <p>Derive our own UI manager to do some specific things: 181 * <ul> 182 * <li>Removing displays</li> 183 * <li>Showing the dashboard</li> 184 * <li>Adding toolbar customization options</li> 185 * <li>Implement the McIDAS-V toolbar as a JToolbar.</li> 186 * <li>Deal with bundles without component groups.</li> 187 * </ul></p> 188 */ 189// TODO: investigate moving similar unpersisting code to persistence manager. 190public class UIManager extends IdvUIManager implements ActionListener { 191 192 private static final Logger logger = LoggerFactory.getLogger(UIManager.class); 193 194 /** Id of the "New Display Tab" menu item for the file menu */ 195 public static final String MENU_NEWDISPLAY_TAB = "file.new.display.tab"; 196 197 /** The tag in the xml ui for creating the special example chooser */ 198 public static final String TAG_EXAMPLECHOOSER = "examplechooser"; 199 200 /** 201 * Used to keep track of ViewManagers inside a bundle. 202 */ 203 public static final HashMap<String, ViewManager> savedViewManagers = 204 new HashMap<>(); 205 206 /** 207 * Property name for whether or not the description field of the support 208 * form should perform line wrapping. 209 * */ 210 public static final String PROP_WRAP_SUPPORT_DESC = 211 "mcidasv.supportform.wrap"; 212 213 /** Action command for manipulating the size of the toolbar icons. */ 214 private static final String ACT_ICON_TYPE = "action.toolbar.seticonsize"; 215 216 /** Action command for removing all displays */ 217 private static final String ACT_REMOVE_DISPLAYS = "action.displays.remove"; 218 219 /** Action command for showing the dashboard */ 220 private static final String ACT_SHOW_DASHBOARD = "action.dashboard.show"; 221 222 /** Action command for showing the dashboard */ 223 private static final String ACT_SHOW_DATASELECTOR = "action.dataselector.show"; 224 225 /** Action command for showing the dashboard */ 226 private static final String ACT_SHOW_DISPLAYCONTROLLER = "action.displaycontroller.show"; 227 228 /** Action command for displaying the toolbar preference tab. */ 229 private static final String ACT_SHOW_PREF = "action.toolbar.showprefs"; 230 231 /** Message shown when an unknown action is in the toolbar. */ 232 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."; 233 234 /** Menu ID for the {@literal "Restore Saved Views"} submenu. */ 235 public static final String MENU_NEWVIEWS = "menu.tools.projections.restoresavedviews"; 236 237 /** Label for the "link" to the toolbar customization preference tab. */ 238 private static final String LBL_TB_EDITOR = "Customize..."; 239 240 /** Current representation of toolbar actions. */ 241 private ToolbarStyle currentToolbarStyle = 242 getToolbarStylePref(ToolbarStyle.MEDIUM); 243 244 /** The IDV property that reflects the size of the icons. */ 245 private static final String PROP_ICON_SIZE = "mcv.ui.iconsize"; 246 247 /** The URL of the script that processes McIDAS-V support requests. */ 248 private static final String SUPPORT_REQ_URL = 249 "https://www.ssec.wisc.edu/mcidas/misc/mc-v/supportreq/support.php"; 250 251 /** Separator to use between window title components. */ 252 protected static final String TITLE_SEPARATOR = " - "; 253 254 /** 255 * The currently "displayed" actions. Keeping this List allows us to get 256 * away with only reading the XML files upon starting the application and 257 * only writing the XML files upon exiting the application. This will avoid 258 * those redrawing delays. 259 */ 260 private List<String> cachedButtons; 261 262 /** Stores all available actions. */ 263 private final IdvActions idvActions; 264 265 /** Map of skin ids to their skin resource index. */ 266 private Map<String, Integer> skinIds = readSkinIds(); 267 268 /** An easy way to figure out who is holding a given ViewManager. */ 269 private Map<ViewManager, ComponentHolder> viewManagers = new HashMap<>(); 270 271 private int componentHolderCount; 272 273 private int componentGroupCount; 274 275 /** Cache for the results of {@link #getWindowTitleFromSkin(int)}. */ 276 private final Map<Integer, String> skinToTitle = new ConcurrentHashMap<>(); 277 278 /** Maps menu IDs to {@link JMenu}s. */ 279// private Hashtable<String, JMenu> menuIds; 280 private Hashtable<String, JMenuItem> menuIds; 281 282 /** The splash screen (minus easter egg). */ 283 private McvSplash splash; 284 285 /** 286 * A list of the toolbars that the IDV is playing with. Used to apply 287 * changes to *all* the toolbars in the application. 288 */ 289 private List<JToolBar> toolbars; 290 291 /** 292 * Keeping the reference to the toolbar menu mouse listener allows us to 293 * avoid constantly rebuilding the menu. 294 */ 295 private MouseListener toolbarMenu; 296 297 /** Keep the dashboard around so we don't have to re-create it each time. */ 298 protected IdvWindow dashboard; 299 300 /** False until {@link #initDone()}. */ 301 protected boolean initDone = false; 302 303 /** IDV instantiation--nice to keep around to reduce getIdv() calls. */ 304 private IntegratedDataViewer idv; 305 306 /** 307 * Hands off our IDV instantiation to IdvUiManager. 308 * 309 * @param idv The idv 310 */ 311 public UIManager(IntegratedDataViewer idv) { 312 super(idv); 313 314 this.idv = idv; 315 316 // cache the appropriate data for the toolbar. it'll make updates 317 // much snappier 318 idvActions = new IdvActions(getIdv(), IdvResourceManager.RSC_ACTIONS); 319 cachedButtons = readToolbar(); 320 } 321 322 /** 323 * Override the IDV method so that we hide component group button. 324 */ 325 @Override public IdvWindow createNewWindow(List viewManagers, 326 boolean notifyCollab, String title, String skinPath, Element skinRoot, 327 boolean show, WindowInfo windowInfo) 328 { 329 330 if (windowInfo != null) { 331 logger.trace("creating window: title='{}' bounds: {}", title, windowInfo.getBounds()); 332 } else { 333 logger.trace("creating window: title='{}' bounds: (no windowinfo)", title); 334 } 335 336 if (Constants.DATASELECTOR_NAME.equals(title)) { 337 show = false; 338 } 339 if (skinPath.indexOf("dashboard.xml") >= 0) { 340 show = false; 341 } 342 343 // used to force any new "display" windows to be the same size as the current window. 344 IdvWindow previousWindow = IdvWindow.getActiveWindow(); 345 346 IdvWindow w = super.createNewWindow(viewManagers, notifyCollab, title, 347 skinPath, skinRoot, show, windowInfo); 348 349 String iconPath = idv.getProperty(Constants.PROP_APP_ICON, (String)null); 350 ImageIcon icon = GuiUtils.getImageIcon(iconPath, getClass(), true); 351 w.setIconImage(icon.getImage()); 352 353 // try to catch the dashboard 354 if (Constants.DATASELECTOR_NAME.equals(w.getTitle())) { 355 setDashboard(w); 356 } else if (!w.getComponentGroups().isEmpty()) { 357 // otherwise we need to hide the component group header and explicitly 358 // set the size of the window. 359 ((ComponentHolder)w.getComponentGroups().get(0)).setShowHeader(false); 360 if (previousWindow != null) { 361 Rectangle r = previousWindow.getBounds(); 362 363 w.setBounds(new Rectangle(r.x, r.y, r.width, r.height)); 364 } 365 } else { 366 logger.trace("creating window with no component groups"); 367 } 368 369 initDisplayShortcuts(w); 370 371 RovingProgress progress = 372 (RovingProgress)w.getComponent(IdvUIManager.COMP_PROGRESSBAR); 373 374 if (progress != null) { 375 progress.start(); 376 } 377 return w; 378 } 379 380 /** 381 * Create the display window described by McIDAS-V's default display skin 382 * 383 * @return {@link IdvWindow} that was created. 384 */ 385 public IdvWindow buildDefaultSkin() { 386 return createNewWindow(new ArrayList(), false); 387 } 388 389 /** 390 * Create a new IdvWindow for the given viewManager. Put the 391 * contents of the viewManager into the window 392 * 393 * @return The new window 394 */ 395 public IdvWindow buildEmptyWindow() { 396 Element root = null; 397 String path = null; 398 String skinName = null; 399 400 path = getIdv().getProperty("mcv.ui.emptycompgroup", (String)null); 401 if (path != null) { 402 path = path.trim(); 403 } 404 405 if ((path != null) && (path.length() > 0)) { 406 try { 407 root = XmlUtil.getRoot(path, getClass()); 408 skinName = getStateManager().getTitle(); 409 String tmp = XmlUtil.getAttribute(root, "name", (String)null); 410 if (tmp == null) { 411 tmp = IOUtil.stripExtension(IOUtil.getFileTail(path)); 412 } 413 skinName = skinName + " - " + tmp; 414 } catch (Exception exc) { 415 logger.error("error building empty window", exc); 416 } 417 } 418 419 IdvWindow window = createNewWindow(new ArrayList(), false, skinName, path, root, true, null); 420 window.setVisible(true); 421 return window; 422 } 423 424 425 /** 426 * Sets {@link #dashboard} to {@code window}. This method also adds some 427 * listeners to {@code window} so that the state of the dashboard is 428 * automatically saved. 429 * 430 * @param window The dashboard. Nothing happens if {@link #dashboard} has 431 * already been set, or this parameter is {@code null}. 432 */ 433 private void setDashboard(final IdvWindow window) { 434 if (window == null || dashboard != null) 435 return; 436 437 dashboard = window; 438 dashboard.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); 439 440 final Component comp = dashboard.getComponent(); 441 final ucar.unidata.idv.StateManager state = getIdv().getStateManager(); 442 443 // for some reason the component listener's "componentHidden" method 444 // would not fire the *first* time the dashboard is closed/hidden. 445 // the window listener catches it. 446 dashboard.addWindowListener(new WindowListener() { 447 public void windowClosed(final WindowEvent e) { 448 Boolean saveViz = (Boolean)state.getPreference(Constants.PREF_SAVE_DASHBOARD_VIZ, Boolean.FALSE); 449 if (saveViz) 450 state.putPreference(Constants.PROP_SHOWDASHBOARD, false); 451 } 452 453 public void windowActivated(final WindowEvent e) { } 454 public void windowClosing(final WindowEvent e) { } 455 public void windowDeactivated(final WindowEvent e) { } 456 public void windowDeiconified(final WindowEvent e) { } 457 public void windowIconified(final WindowEvent e) { } 458 public void windowOpened(final WindowEvent e) { } 459 }); 460 461 dashboard.getComponent().addComponentListener(new ComponentListener() { 462 public void componentMoved(final ComponentEvent e) { 463 state.putPreference(Constants.PROP_DASHBOARD_BOUNDS, comp.getBounds()); 464 } 465 466 public void componentResized(final ComponentEvent e) { 467 state.putPreference(Constants.PROP_DASHBOARD_BOUNDS, comp.getBounds()); 468 } 469 470 public void componentShown(final ComponentEvent e) { 471 Boolean saveViz = (Boolean)state.getPreference(Constants.PREF_SAVE_DASHBOARD_VIZ, Boolean.FALSE); 472 if (saveViz) 473 state.putPreference(Constants.PROP_SHOWDASHBOARD, true); 474 } 475 476 public void componentHidden(final ComponentEvent e) { 477 Boolean saveViz = (Boolean)state.getPreference(Constants.PREF_SAVE_DASHBOARD_VIZ, Boolean.FALSE); 478 if (saveViz) 479 state.putPreference(Constants.PROP_SHOWDASHBOARD, false); 480 } 481 }); 482 483 Rectangle bounds = (Rectangle)state.getPreferenceOrProperty(Constants.PROP_DASHBOARD_BOUNDS); 484 if (bounds != null) 485 comp.setBounds(bounds); 486 } 487 488 /** 489 * Attempts to add all component holders in {@code info} to 490 * {@code group}. Especially useful when unpersisting a bundle and 491 * attempting to deal with its component groups. 492 * 493 * @param info The window we want to process. 494 * @param group Receives the holders in {@code info}. 495 * 496 * @return True if there were component groups in {@code info}. 497 */ 498 public boolean unpersistComponentGroups(final WindowInfo info, 499 final McvComponentGroup group) { 500 Collection<Object> comps = info.getPersistentComponents().values(); 501 502 if (comps.isEmpty()) { 503 return false; 504 } 505 506 for (Object comp : comps) { 507 // comp is typically always an IdvComponentGroup, but there are 508 // no guarantees... 509 if (! (comp instanceof IdvComponentGroup)) { 510 System.err.println("DEBUG: non IdvComponentGroup found in persistent components: " 511 + comp.getClass().getName()); 512 continue; 513 } 514 515 IdvComponentGroup bundleGroup = (IdvComponentGroup)comp; 516 517 // need to make a copy of this list to avoid a rogue 518 // ConcurrentModificationException 519 // TODO: determine which threads are clobbering each other. 520 List<IdvComponentHolder> holders = 521 new ArrayList<>(bundleGroup.getDisplayComponents()); 522 523 for (IdvComponentHolder holder : holders) { 524 group.quietAddComponent(holder); 525 } 526 527 group.redoLayout(); 528 529 JComponent groupContents = group.getContents(); 530 groupContents.setPreferredSize(groupContents.getSize()); 531 } 532 return true; 533 } 534 535 /** 536 * Override IdvUIManager's loadLookAndFeel so that we can force the IDV to 537 * load the Aqua look and feel if requested from the command line. 538 */ 539 @Override public void loadLookAndFeel() { 540 if (McIDASV.useAquaLookAndFeel) { 541 // since we must rely on the IDV to do the actual loading (due to 542 // our UIManager's name conflicting with javax.swing.UIManager's 543 // name), save the user's preference, replace it temporarily and 544 // have the IDV do its thing, then overwrite the temp preference 545 // with the saved preference. Blah! 546 String previousLF = getStore().get(PREF_LOOKANDFEEL, (String)null); 547 getStore().put(PREF_LOOKANDFEEL, "apple.laf.AquaLookAndFeel"); 548 super.loadLookAndFeel(); 549 getStore().put(PREF_LOOKANDFEEL, previousLF); 550 551 // locale code was taken out of IDVUIManager's "loadLookAndFeel". 552 // 553 // if the locale preference is set to "system", there will *NOT* 554 // be a PREF_LOCALE -> "Locale.EXAMPLE" key/value pair in main.xml; 555 // as you're using the default state of the preference. 556 // this also means that if there is a non-null value associated 557 // with PREF_LOCALE, the user has selected the US locale. 558 String locale = getStore().get(PREF_LOCALE, (String)null); 559 if (locale != null) { 560 Locale.setDefault(Locale.US); 561 } 562 } else { 563 super.loadLookAndFeel(); 564 } 565 } 566 567 @Override public void handleWindowActivated(final IdvWindow window) { 568 List<ViewManager> viewManagers = window.getViewManagers(); 569 ViewManager newActive = null; 570 long lastActivatedTime = -1; 571 572 for (ViewManager viewManager : viewManagers) { 573 if (viewManager.getContents() == null) 574 continue; 575 576 if (!viewManager.getContents().isVisible()) 577 continue; 578 579 lastActiveFrame = window; 580 581 if (viewManager.getLastTimeActivated() > lastActivatedTime) { 582 newActive = viewManager; 583 lastActivatedTime = viewManager.getLastTimeActivated(); 584 } 585 } 586 587 if (newActive != null) 588 getVMManager().setLastActiveViewManager(newActive); 589 } 590 591 /** 592 * Handles the windowing portions of bundle loading: wraps things in 593 * component groups (if needed), merges things into existing windows or 594 * creates new windows, and removes displays and data if asked nicely. 595 * 596 * @param windows WindowInfos from the bundle. 597 * @param newViewManagers ViewManagers stored in the bundle. 598 * @param okToMerge Put bundled things into an existing window? 599 * @param fromCollab Did this come from the collab stuff? 600 * @param didRemoveAll Remove all data and displays? 601 * 602 * @see IdvUIManager#unpersistWindowInfo(List, List, boolean, boolean, 603 * boolean) 604 */ 605 @Override public void unpersistWindowInfo(List windows, 606 List newViewManagers, boolean okToMerge, boolean fromCollab, 607 boolean didRemoveAll) 608 { 609 if (newViewManagers == null) { 610 newViewManagers = new ArrayList<>(); 611 } 612 613 // keep track of the "old" state if the user wants to remove things. 614 boolean mergeLayers = ((PersistenceManager)getPersistenceManager()).getMergeBundledLayers(); 615 List<IdvComponentHolder> holdersBefore = new ArrayList<>(); 616 List<IdvWindow> windowsBefore = new ArrayList<>(); 617 if (didRemoveAll) { 618 holdersBefore.addAll(McVGuiUtils.getAllComponentHolders()); 619 windowsBefore.addAll(McVGuiUtils.getAllDisplayWindows()); 620 } 621 622 for (WindowInfo info : (List<WindowInfo>)windows) { 623 newViewManagers.removeAll(info.getViewManagers()); 624 makeBundledDisplays(info, okToMerge, mergeLayers, fromCollab); 625 626 if (mergeLayers) { 627 holdersBefore.addAll(McVGuiUtils.getComponentHolders(info)); 628 } 629 } 630// System.err.println("holdersBefore="+holdersBefore); 631 // no reason to kill the displays if there aren't any windows in the 632 // bundle! 633 if ((mergeLayers) || (didRemoveAll && !windows.isEmpty())) { 634 killOldDisplays(holdersBefore, windowsBefore, (okToMerge || mergeLayers)); 635 } 636 } 637 638 /** 639 * Removes data and displays that existed prior to loading a bundle. 640 * 641 * @param oldHolders Component holders around before loading. 642 * @param oldWindows Windows around before loading. 643 * @param merge Were the bundle contents merged into an existing window? 644 */ 645 public void killOldDisplays(final List<IdvComponentHolder> oldHolders, 646 final List<IdvWindow> oldWindows, final boolean merge) 647 { 648// System.err.println("killOldDisplays: merge="+merge); 649 // if we merged, this will ensure that any old holders in the merged 650 // window also get removed. 651 if (merge) { 652 for (IdvComponentHolder holder : oldHolders) { 653 holder.doRemove(); 654 } 655 } 656 657 // mop up any windows that no longer have component holders. 658 for (IdvWindow window : oldWindows) { 659 IdvComponentGroup group = McVGuiUtils.getComponentGroup(window); 660 661 List<IdvComponentHolder> holders = 662 McVGuiUtils.getComponentHolders(group); 663 664 // if the old set of holders contains all of this window's 665 // holders, this window can be deleted: 666 // 667 // this works fine for merging because the okToMerge stuff will 668 // remove all old holders from the current window, but if the 669 // bundle was merged into this window, containsAll() will fail 670 // due to there being a new holder. 671 // 672 // if the bundle was loaded into its own window, then 673 // all the old windows will pass this test. 674 if (oldHolders.containsAll(holders)) { 675 group.doRemove(); 676 window.dispose(); 677 } 678 } 679 } 680 681 682 /** 683 * A hack because Unidata moved the skins (taken from 684 * {@link IdvPersistenceManager}). 685 * 686 * @param skinPath original path 687 * @return fixed path 688 */ 689 private String fixSkinPath(String skinPath) { 690 if (skinPath == null) { 691 return null; 692 } 693 if (StringUtil.stringMatch( 694 skinPath, "^/ucar/unidata/idv/resources/[^/]+\\.xml")) { 695 skinPath = 696 StringUtil.replace(skinPath, "/ucar/unidata/idv/resources/", 697 "/ucar/unidata/idv/resources/skins/"); 698 } 699 return skinPath; 700 } 701 702 /** 703 * <p> 704 * Uses the contents of {@code info} to rebuild a display that has been 705 * bundled. If {@code merge} is true, the displayable parts of the bundle 706 * will be put into the current window. Otherwise a new window is created 707 * and the relevant parts of the bundle will occupy that new window. 708 * </p> 709 * 710 * @param info WindowInfo to use with creating the new window. 711 * @param merge Merge created things into an existing window? 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(new Runnable() { 903 public void run() { 904 new AboutFrame((McIDASV)idv).setVisible(true); 905 } 906 }); 907 } 908 909 /** 910 * Handles all the ActionEvents that occur for widgets contained within 911 * this class. It's not so pretty, but it isolates the event handling in 912 * one place (and reduces the number of action listeners to one). 913 * 914 * @param e The event that triggered the call to this method. 915 */ 916 public void actionPerformed(ActionEvent e) { 917 String cmd = (String)e.getActionCommand(); 918 boolean toolbarEditEvent = false; 919 920 // handle selecting large icons 921 if (cmd.startsWith(ToolbarStyle.LARGE.getAction())) { 922 currentToolbarStyle = ToolbarStyle.LARGE; 923 toolbarEditEvent = true; 924 } 925 926 // handle selecting medium icons 927 else if (cmd.startsWith(ToolbarStyle.MEDIUM.getAction())) { 928 currentToolbarStyle = ToolbarStyle.MEDIUM; 929 toolbarEditEvent = true; 930 } 931 932 // handle selecting small icons 933 else if (cmd.startsWith(ToolbarStyle.SMALL.getAction())) { 934 currentToolbarStyle = ToolbarStyle.SMALL; 935 toolbarEditEvent = true; 936 } 937 938 // handle the user selecting the show toolbar preference menu item 939 else if (cmd.startsWith(ACT_SHOW_PREF)) { 940 IdvPreferenceManager prefs = idv.getPreferenceManager(); 941 prefs.showTab(Constants.PREF_LIST_TOOLBAR); 942 toolbarEditEvent = true; 943 } 944 945 // handle the user toggling the size of the icon 946 else if (cmd.startsWith(ACT_ICON_TYPE)) 947 toolbarEditEvent = true; 948 949 // handle the user removing displays 950 else if (cmd.startsWith(ACT_REMOVE_DISPLAYS)) 951 idv.removeAllDisplays(); 952 953 // handle popping up the dashboard. 954 else if (cmd.startsWith(ACT_SHOW_DASHBOARD)) 955 showDashboard(); 956 957 // handle popping up the data explorer. 958 else if (cmd.startsWith(ACT_SHOW_DATASELECTOR)) 959 showDashboard("Data Sources"); 960 961 // handle popping up the display controller. 962 else if (cmd.startsWith(ACT_SHOW_DISPLAYCONTROLLER)) 963 showDashboard("Layer Controls"); 964 965 else 966 System.err.println("Unsupported action event!"); 967 968 // if the user did something to change the toolbar, hide the current 969 // toolbar, replace it, and then make the new toolbar visible. 970 if (toolbarEditEvent == true) { 971 972 getStateManager().writePreference(PROP_ICON_SIZE, 973 currentToolbarStyle.getSizeAsString()); 974 975 // destroy the menu so it can be properly updated during rebuild 976 toolbarMenu = null; 977 978 for (JToolBar toolbar : toolbars) { 979 toolbar.setVisible(false); 980 populateToolbar(toolbar); 981 toolbar.setVisible(true); 982 } 983 } 984 } 985 986 public JComponent getDisplaySelectorComponent() { 987 DefaultMutableTreeNode root = new DefaultMutableTreeNode(""); 988 DefaultTreeModel model = new DefaultTreeModel(root); 989 final JTree tree = new JTree(model); 990 tree.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); 991 tree.getSelectionModel().setSelectionMode( 992 TreeSelectionModel.SINGLE_TREE_SELECTION 993 ); 994 DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer(); 995 renderer.setIcon(null); 996 renderer.setOpenIcon(null); 997 renderer.setClosedIcon(null); 998 tree.setCellRenderer(renderer); 999 1000 for (IdvWindow w : McVGuiUtils.getAllDisplayWindows()) { 1001 String title = w.getTitle(); 1002 TwoFacedObject winTFO = new TwoFacedObject(title, w); 1003 DefaultMutableTreeNode winNode = new DefaultMutableTreeNode(winTFO); 1004 for (IdvComponentHolder h : McVGuiUtils.getComponentHolders(w)) { 1005 String hName = h.getName(); 1006 TwoFacedObject tmp = new TwoFacedObject(hName, h); 1007 DefaultMutableTreeNode holderNode = new DefaultMutableTreeNode(tmp); 1008 //for (ViewManager v : (List<ViewManager>)h.getViewManagers()) { 1009 for (int i = 0; i < h.getViewManagers().size(); i++) { 1010 ViewManager v = (ViewManager)h.getViewManagers().get(i); 1011 String vName = v.getName(); 1012 TwoFacedObject tfo = null; 1013 1014 if (vName != null && vName.length() > 0) 1015 tfo = new TwoFacedObject(vName, v); 1016 else 1017 tfo = new TwoFacedObject(Constants.PANEL_NAME + " " + (i+1), v); 1018 1019 holderNode.add(new DefaultMutableTreeNode(tfo)); 1020 } 1021 winNode.add(holderNode); 1022 } 1023 root.add(winNode); 1024 } 1025 1026 // select the appropriate view 1027 tree.addTreeSelectionListener(new TreeSelectionListener() { 1028 public void valueChanged(TreeSelectionEvent evt) { 1029 DefaultMutableTreeNode node = (DefaultMutableTreeNode)tree.getLastSelectedPathComponent(); 1030 if (node == null || !(node.getUserObject() instanceof TwoFacedObject)) { 1031 return; 1032 } 1033 TwoFacedObject tfo = (TwoFacedObject) node.getUserObject(); 1034 1035 Object obj = tfo.getId(); 1036 if (obj instanceof ViewManager) { 1037 ViewManager viewManager = (ViewManager) tfo.getId(); 1038 idv.getVMManager().setLastActiveViewManager(viewManager); 1039 } else if (obj instanceof McvComponentHolder) { 1040 McvComponentHolder holder = (McvComponentHolder)obj; 1041 holder.setAsActiveTab(); 1042 } else if (obj instanceof IdvWindow) { 1043 IdvWindow window = (IdvWindow)obj; 1044 window.toFront(); 1045 } 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(new ActionListener() { 1386 1387 public void actionPerformed(ActionEvent e) { 1388 // running in a separate thread is kinda nice! *HEH* 1389 Misc.run(UIManager.this, "processBundle", bundle); 1390 } 1391 }); 1392 toolbar.add(button); 1393 } 1394 1395 /** 1396 * Handle loading the given {@link ucar.unidata.idv.SavedBundle SavedBundle}. 1397 * 1398 * <p>Overridden in McIDAS-V to allow {@literal "default"} bundles to 1399 * show the same bundle loading dialog as a {@literal "favorite"} bundle. 1400 * </p> 1401 * 1402 * @param bundle Bundle to process. Cannot be {@code null}. 1403 */ 1404 @Override public void processBundle(SavedBundle bundle) { 1405 showWaitCursor(); 1406 LogUtil.message("Loading bundle: " + bundle.getName()); 1407 int type = bundle.getType(); 1408 boolean checkToRemove = (type == SavedBundle.TYPE_FAVORITE) || (type == SavedBundle.TYPE_SYSTEM); 1409 boolean ok = getPersistenceManager().decodeXmlFile(bundle.getUrl(), 1410 bundle.getName(), 1411 checkToRemove); 1412 if (ok && (bundle.getType() == SavedBundle.TYPE_DATA)) { 1413 showDataSelector(); 1414 } 1415 LogUtil.message(""); 1416 showNormalCursor(); 1417 } 1418 1419 /** 1420 * Builds two things, given a toolbar and a tree node: a JButton that 1421 * represents a {@literal "first-level"} parent node and a JPopupMenu that 1422 * appears upon clicking the JButton. The button is then added to the given 1423 * toolbar. 1424 * 1425 * <p>{@literal "First-level"} means the given node is a child of the root 1426 * node.</p> 1427 * 1428 * @param toolbar Toolbar to which we add the bundle tree. 1429 * @param node Node we want to add. 1430 */ 1431 private void addBundleTree(JToolBar toolbar, BundleTreeNode node) { 1432 ImageIcon catIcon = 1433 GuiUtils.getImageIcon("/auxdata/ui/icons/Folder.gif"); 1434 1435 final JButton button = new JButton(node.getName(), catIcon); 1436 final JPopupMenu popup = new JPopupMenu(); 1437 1438 button.setToolTipText("Show Favorites category: " + node.getName()); 1439 1440 button.addActionListener(new ActionListener() { 1441 public void actionPerformed(ActionEvent e) { 1442 popup.show(button, 0, button.getHeight()); 1443 } 1444 }); 1445 1446 toolbar.add(button); 1447 1448 // recurse through the child nodes 1449 for (BundleTreeNode kid : node.getChildren()) { 1450 buildPopupMenu(kid, popup); 1451 } 1452 } 1453 1454 /** 1455 * Writes the currently displayed toolbar buttons to the toolbar XML. This 1456 * has mostly been ripped off from ToolbarEditor. :( 1457 */ 1458 public void writeToolbar() { 1459 XmlResourceCollection resources = 1460 getResourceManager() 1461 .getXmlResources(IdvResourceManager.RSC_TOOLBAR); 1462 1463 String actionPrefix = "action:"; 1464 1465 // ensure that the IDV can read the XML we're generating. 1466 Document doc = resources.getWritableDocument("<panel/>"); 1467 Element root = resources.getWritableRoot("<panel/>"); 1468 root.setAttribute(XmlUi.ATTR_LAYOUT, XmlUi.LAYOUT_FLOW); 1469 root.setAttribute(XmlUi.ATTR_MARGIN, "4"); 1470 root.setAttribute(XmlUi.ATTR_VSPACE, "0"); 1471 root.setAttribute(XmlUi.ATTR_HSPACE, "2"); 1472 root.setAttribute(XmlUi.inheritName(XmlUi.ATTR_SPACE), "2"); 1473 root.setAttribute(XmlUi.inheritName(XmlUi.ATTR_WIDTH), "5"); 1474 1475 // clear out any pesky kids from previous relationships. XML don't need 1476 // no baby-mama drama. 1477 XmlUtil.removeChildren(root); 1478 1479 // iterate through the actions that have toolbar buttons in use and add 1480 // 'em to the XML. 1481 for (String action : cachedButtons) { 1482 Element e; 1483 if (action != null) { 1484 e = doc.createElement(XmlUi.TAG_BUTTON); 1485 e.setAttribute(XmlUi.ATTR_ACTION, (actionPrefix + action)); 1486 } else { 1487 e = doc.createElement(XmlUi.TAG_FILLER); 1488 e.setAttribute(XmlUi.ATTR_WIDTH, "5"); 1489 } 1490 root.appendChild(e); 1491 } 1492 1493 // write the XML 1494 try { 1495 resources.writeWritable(); 1496 } catch (Exception e) { 1497 e.printStackTrace(); 1498 } 1499 } 1500 1501 /** 1502 * Read the contents of the toolbar XML into a List. We're essentially just 1503 * throwing actions into the list. 1504 * 1505 * @return The actions/buttons that live in the toolbar xml. Note that if 1506 * an element is {@code null}, this element represents a {@literal "space"} 1507 * that should appear in both the Toolbar and the Toolbar Preferences. 1508 */ 1509 public List<String> readToolbar() { 1510 final Element root = getToolbarRoot(); 1511 if (root == null) { 1512 return null; 1513 } 1514 1515 final NodeList elements = XmlUtil.getElements(root); 1516 List<String> data = new ArrayList<String>(elements.getLength()); 1517 for (int i = 0; i < elements.getLength(); i++) { 1518 Element child = (Element)elements.item(i); 1519 if (child.getTagName().equals(XmlUi.TAG_BUTTON)) { 1520 data.add( 1521 XmlUtil.getAttribute(child, ATTR_ACTION, (String)null) 1522 .substring(7)); 1523 } else { 1524 data.add(null); 1525 } 1526 } 1527 return data; 1528 } 1529 1530 /** 1531 * Returns the icon associated with {@code actionId}. Note that associating 1532 * the {@literal "missing icon"} icon with an action is allowable. 1533 * 1534 * @param actionId Action ID whose associated icon is to be returned. 1535 * @param style Returned icon's size will be the size associated with the 1536 * specified {@code ToolbarStyle}. 1537 * 1538 * @return Either the icon corresponding to {@code actionId} or the default 1539 * {@literal "missing icon"} icon. 1540 * 1541 * @throws NullPointerException if {@code actionId} is null. 1542 */ 1543 protected Icon getActionIcon(final String actionId, 1544 final ToolbarStyle style) 1545 { 1546 if (actionId == null) 1547 throw new NullPointerException("Action ID cannot be null"); 1548 1549 Icon actionIcon = idvActions.getStyledIconFor(actionId, style); 1550 if (actionIcon != null) 1551 return actionIcon; 1552 1553 String icon = "/edu/wisc/ssec/mcidasv/resources/icons/toolbar/range-bearing%d.png"; 1554 URL tmp = getClass().getResource(String.format(icon, style.getSize())); 1555 return new ImageIcon(tmp); 1556 } 1557 1558 /** 1559 * Returns the known {@link IdvAction}s in the form of {@link IdvActions}. 1560 * 1561 * @return {@link #idvActions} 1562 */ 1563 public IdvActions getCachedActions() { 1564 return idvActions; 1565 } 1566 1567 /** 1568 * Returns the actions that currently make up the McIDAS-V toolbar. 1569 * 1570 * @return {@link List} of {@link ActionAttribute#ID}s that make up the 1571 * current toolbar buttons. 1572 */ 1573 public List<String> getCachedButtons() { 1574 if (cachedButtons == null) { 1575 cachedButtons = readToolbar(); 1576 } 1577 return cachedButtons; 1578 } 1579 1580 /** 1581 * Make the menu of actions. 1582 * 1583 * <p>Overridden in McIDAS-V so that we can fool the IDV into working with 1584 * our icons that allow for multiple {@literal "styles"}. 1585 * 1586 * @param obj Object to call. 1587 * @param method Method to call. 1588 * @param makeCall if {@code true}, call 1589 * {@link IntegratedDataViewer#handleAction(String)}. 1590 * 1591 * @return List of {@link JMenu}s that represent our action menus. 1592 */ 1593 @Override public List<JMenu> makeActionMenu(final Object obj, 1594 final String method, final boolean makeCall) 1595 { 1596 List<JMenu> menu = arrList(); 1597 IdvActions actions = getCachedActions(); 1598 for (String group : actions.getAllGroups()) { 1599 List<JMenuItem> items = arrList(); 1600 for (IdvAction action : actions.getActionsForGroup(group)) { 1601 String cmd = (makeCall) ? action.getCommand() : action.getId(); 1602 String desc = action.getAttribute(ActionAttribute.DESCRIPTION); 1603// items.add(GuiUtils.makeMenuItem(desc, obj, method, cmd)); 1604 items.add(makeMenuItem(desc, obj, method, cmd)); 1605 } 1606// menu.add(GuiUtils.makeMenu(group, items)); 1607 menu.add(makeMenu(group, items)); 1608 } 1609 return menu; 1610 } 1611 1612 /** 1613 * @see ucar.unidata.util.MenuUtil#makeMenuItem(String, Object, String, Object) 1614 */ 1615 public static JMenuItem makeMenuItem(String label, Object obj, 1616 String method, Object arg) 1617 { 1618 return MenuUtil.makeMenuItem(label, obj, method, arg); 1619 } 1620 1621 /** 1622 * @see ucar.unidata.util.MenuUtil#makeMenu(String, List) 1623 */ 1624 @SuppressWarnings("unchecked") 1625 public static JMenu makeMenu(String name, List menuItems) { 1626 return MenuUtil.makeMenu(name, menuItems); 1627 } 1628 1629 /** 1630 * Returns the collection of action identifiers. 1631 * 1632 * <p>Overridden in McIDAS-V so that we can fool the IDV into working with 1633 * our icons that allow for multiple {@literal "styles"}. 1634 * 1635 * @return {@link List} of {@link String}s that correspond to 1636 * {@link IdvAction IdvActions}. 1637 */ 1638 @Override public List<String> getActions() { 1639 return idvActions.getAttributes(ActionAttribute.ID); 1640 } 1641 1642 /** 1643 * Looks for the XML {@link Element} representation of the action 1644 * associated with {@code actionId}. 1645 * 1646 * <p>Overridden in McIDAS-V so that we can fool the IDV into working with 1647 * our icons that allow for multiple {@literal "styles"}. 1648 * 1649 * @param actionId ID of the action whose {@literal "action node"} is desired. Cannot be {@code null}. 1650 * 1651 * @return {@literal "action node"} associated with {@code actionId}. 1652 * 1653 * @throws NullPointerException if {@code actionId} is {@code null}. 1654 */ 1655 @Override public Element getActionNode(final String actionId) { 1656 requireNonNull(actionId, "Null action id strings are invalid"); 1657 return idvActions.getElementForAction(actionId); 1658 } 1659 1660 /** 1661 * Searches for an action identified by a given {@code actionId}, and 1662 * returns the value associated with its {@code attr}. 1663 * 1664 * <p>Overridden in McIDAS-V so that we can fool the IDV into working with 1665 * our icons that allow for multiple {@literal "styles"}. 1666 * 1667 * @param actionId ID of the action whose attribute value is desired. Cannot be {@code null}. 1668 * @param attr The attribute whose value is desired. Cannot be {@code null}. 1669 * 1670 * @return Value associated with the given action and given attribute. 1671 * 1672 * @throws NullPointerException if {@code actionId} or {@code attr} is {@code null}. 1673 */ 1674 @Override public String getActionAttr(final String actionId, 1675 final String attr) 1676 { 1677 requireNonNull(actionId, "Null action id strings are invalid"); 1678 requireNonNull(attr, "Null attributes are invalid"); 1679 ActionAttribute actionAttr = ActionAttribute.valueOf(attr.toUpperCase()); 1680 return idvActions.getAttributeForAction(stripAction(actionId), actionAttr); 1681 } 1682 1683 /** 1684 * Attempts to verify that {@code element} represents a {@literal "valid"} 1685 * IDV action. 1686 * 1687 * @param element {@link Element} to check. {@code null} values permitted, 1688 * but they return {@code false}. 1689 * 1690 * @return {@code true} if {@code element} had all required 1691 * {@link ActionAttribute}s. {@code false} otherwise, or if 1692 * {@code element} is {@code null}. 1693 */ 1694 private static boolean isValidIdvAction(final Element element) { 1695 if (element == null) { 1696 return false; 1697 } 1698 for (ActionAttribute attribute : ActionAttribute.values()) { 1699 if (!attribute.isRequired()) { 1700 continue; 1701 } 1702 if (!XmlUtil.hasAttribute(element, attribute.asIdvString())) { 1703 return false; 1704 } 1705 } 1706 return true; 1707 } 1708 1709 /** 1710 * Builds a {@link Map} of {@link ActionAttribute}s to values for a given 1711 * {@link Element}. If {@code element} does not contain an optional attribute, 1712 * use the attribute's default value. 1713 * 1714 * @param element {@literal "Action node"} of interest. {@code null} 1715 * permitted, but results in an empty {@code Map}. 1716 * 1717 * @return Mapping of {@code ActionAttribute}s to values, or an empty 1718 * {@code Map} if {@code element} is {@code null}. 1719 */ 1720 private static Map<ActionAttribute, String> actionElementToMap( 1721 final Element element) 1722 { 1723 if (element == null) { 1724 return Collections.emptyMap(); 1725 } 1726 // loop through set of action attributes; if element contains attribute "A", add it; return results. 1727 Map<ActionAttribute, String> attrs = new LinkedHashMap<>(); 1728 for (ActionAttribute attribute : ActionAttribute.values()) { 1729 String idvStr = attribute.asIdvString(); 1730 if (XmlUtil.hasAttribute(element, idvStr)) { 1731 attrs.put(attribute, XmlUtil.getAttribute(element, idvStr)); 1732 } else { 1733 attrs.put(attribute, attribute.defaultValue()); 1734 } 1735 } 1736 return attrs; 1737 } 1738 1739 /** 1740 * <p> 1741 * Builds a tree out of the bundles that should appear within the McV 1742 * toolbar. A tree is a nice way to store this data, as the default IDV 1743 * behavior is to act kinda like a file explorer when it displays these 1744 * bundles. 1745 * </p> 1746 * 1747 * <p> 1748 * The tree makes it REALLY easy to replicate the default IDV 1749 * functionality. 1750 * </p> 1751 * 1752 * @param bundleType One of {@link ucar.unidata.idv.SavedBundle#TYPE_FAVORITE}, 1753 * {@link ucar.unidata.idv.SavedBundle#TYPE_DISPLAY}, 1754 * {@link ucar.unidata.idv.SavedBundle#TYPE_DATA} or 1755 * {@link ucar.unidata.idv.SavedBundle#TYPE_SYSTEM}. 1756 * 1757 * @return The root BundleTreeNode for the tree containing toolbar bundles. 1758 */ 1759 public BundleTreeNode buildBundleTree(int bundleType) { 1760 final String TOOLBAR = "Toolbar"; 1761 1762 final List<SavedBundle> bundles = 1763 getPersistenceManager().getBundles(bundleType); 1764 1765 // handy reference to parent nodes; bundle count * 4 seems pretty safe 1766 final Map<String, BundleTreeNode> mapper = new HashMap<>(bundles.size() * 4); 1767 1768 // iterate through all toolbar bundles 1769 for (SavedBundle bundle : bundles) { 1770 String categoryPath = ""; 1771 String grandParentPath; 1772 1773 // build the "path" to the bundle. these paths basically look like 1774 // "Toolbar>category>subcategory>." so "category" is a category of 1775 // toolbar bundles and subcategory is a subcategory of that. The 1776 // IDV will build nice JPopupMenus with everything that appears in 1777 // "category," so McV needs to do the same thing. thus McV needs to 1778 // figure out the complete path to each toolbar bundle! 1779 List<String> categories = (List<String>)bundle.getCategories(); 1780 if (categories == null || categories.isEmpty() || !TOOLBAR.equals(categories.get(0))) { 1781 continue; 1782 } 1783 1784 for (String category : categories) { 1785 grandParentPath = categoryPath; 1786 categoryPath += category + '>'; 1787 1788 if (!mapper.containsKey(categoryPath)) { 1789 BundleTreeNode grandParent = mapper.get(grandParentPath); 1790 BundleTreeNode parent = new BundleTreeNode(category); 1791 if (grandParent != null) { 1792 grandParent.addChild(parent); 1793 } 1794 mapper.put(categoryPath, parent); 1795 } 1796 } 1797 1798 // so the tree book-keeping (if any) is done and we can just add 1799 // the current SavedBundle to its parent node within the tree. 1800 BundleTreeNode parent = mapper.get(categoryPath); 1801 parent.addChild(new BundleTreeNode(bundle.getName(), bundle)); 1802 } 1803 1804 // return the root of the tree. 1805 return mapper.get("Toolbar>"); 1806 } 1807 1808 /** 1809 * Recursively builds the contents of the (first call) JPopupMenu. This is 1810 * where that tree annoyance stuff comes in handy. This is basically a 1811 * simple tree traversal situation. 1812 * 1813 * @param node The node that we're trying to use to build the contents. 1814 * @param comp The component to which we add node contents. 1815 */ 1816 private void buildPopupMenu(BundleTreeNode node, JComponent comp) { 1817 // if the current node has no bundle, it's considered a parent node 1818 if (node.getBundle() == null) { 1819 // parent nodes mean that we have to create a JMenu and add it 1820 JMenu test = new JMenu(node.getName()); 1821 comp.add(test); 1822 1823 // recurse through children to continue building. 1824 for (BundleTreeNode kid : node.getChildren()) { 1825 buildPopupMenu(kid, test); 1826 } 1827 1828 } else { 1829 // nodes with bundles can simply be added to the JMenu 1830 // (or JPopupMenu) 1831 JMenuItem mi = new JMenuItem(node.getName()); 1832 final SavedBundle theBundle = node.getBundle(); 1833 1834 mi.addActionListener(new ActionListener() { 1835 public void actionPerformed(ActionEvent ae) { 1836 //Do it in a thread 1837 Misc.run(UIManager.this, "processBundle", theBundle); 1838 } 1839 }); 1840 1841 comp.add(mi); 1842 } 1843 } 1844 1845 @Override public void initDone() { 1846 super.initDone(); 1847 if (getStore().get(Constants.PREF_VERSION_CHECK, true)) { 1848 StateManager stateManager = (StateManager)getStateManager(); 1849 stateManager.checkForNewerVersion(false); 1850 stateManager.checkForNotice(false); 1851 } 1852 1853 // not super excited about how this works. 1854 // showBasicWindow(true); 1855 1856 initDone = true; 1857 showDashboard(); 1858 } 1859 1860 /** 1861 * Create the splash screen if needed 1862 */ 1863 public void initSplash() { 1864 if (getProperty(PROP_SHOWSPLASH, true) 1865 && !getArgsManager().getNoGui() 1866 && !getArgsManager().getIsOffScreen() 1867 && !getArgsManager().testMode) { 1868 splash = new McvSplash(idv); 1869 splashMsg("Loading Programs"); 1870 } 1871 } 1872 1873 /** 1874 * Create (if null) and show the HelpTipDialog. If checkPrefs is true 1875 * then only create the dialog if the PREF_HELPTIPSHOW preference is true. 1876 * 1877 * @param checkPrefs Should the user preferences be checked 1878 */ 1879 /** THe help tip dialog */ 1880 private McvHelpTipDialog helpTipDialog; 1881 1882 public void initHelpTips(boolean checkPrefs) { 1883 try { 1884 if (getIdv().getArgsManager().getIsOffScreen()) { 1885 return; 1886 } 1887 if (checkPrefs) { 1888 if (!getStore().get(McvHelpTipDialog.PREF_HELPTIPSHOW, true)) { 1889 return; 1890 } 1891 } 1892 if (helpTipDialog == null) { 1893 IdvResourceManager resourceManager = getResourceManager(); 1894 helpTipDialog = new McvHelpTipDialog( 1895 resourceManager.getXmlResources( 1896 resourceManager.RSC_HELPTIPS), getIdv(), getStore(), 1897 getIdvClass(), 1898 getStore().get( 1899 McvHelpTipDialog.PREF_HELPTIPSHOW, true)); 1900 } 1901 helpTipDialog.setVisible(true); 1902 GuiUtils.toFront(helpTipDialog); 1903 } catch (Throwable excp) { 1904 logException("Reading help tips", excp); 1905 } 1906 } 1907 1908 /** 1909 * If created, close the HelpTipDialog window. 1910 */ 1911 public void closeHelpTips() { 1912 if (helpTipDialog != null) { 1913 helpTipDialog.setVisible(false); 1914 } 1915 } 1916 1917 /** 1918 * Create (if null) and show the HelpTipDialog 1919 */ 1920 public void showHelpTips() { 1921 initHelpTips(false); 1922 } 1923 1924 /** 1925 * Populate a menu with bundles known to the {@code PersistenceManager}. 1926 * 1927 * @param inBundleMenu The menu to populate 1928 */ 1929 public void makeBundleMenu(JMenu inBundleMenu) { 1930 final int bundleType = IdvPersistenceManager.BUNDLES_FAVORITES; 1931 1932 JMenuItem mi; 1933 mi = new JMenuItem("Manage..."); 1934 McVGuiUtils.setMenuImage(mi, Constants.ICON_FAVORITEMANAGE_SMALL); 1935 mi.setMnemonic(GuiUtils.charToKeyCode("M")); 1936 inBundleMenu.add(mi); 1937 mi.addActionListener(new ActionListener() { 1938 public void actionPerformed(ActionEvent ae) { 1939 showBundleDialog(bundleType); 1940 } 1941 }); 1942 1943 final List bundles = getPersistenceManager().getBundles(bundleType); 1944 if (bundles.isEmpty()) { 1945 return; 1946 } 1947 final String title = 1948 getPersistenceManager().getBundleTitle(bundleType); 1949 final String bundleDir = 1950 getPersistenceManager().getBundleDirectory(bundleType); 1951 1952 JMenu bundleMenu = new JMenu(title); 1953 McVGuiUtils.setMenuImage(bundleMenu, Constants.ICON_FAVORITE_SMALL); 1954 bundleMenu.setMnemonic(GuiUtils.charToKeyCode(title)); 1955 1956// getPersistenceManager().initBundleMenu(bundleType, bundleMenu); 1957 1958 Hashtable catMenus = new Hashtable(); 1959 inBundleMenu.addSeparator(); 1960 inBundleMenu.add(bundleMenu); 1961 for (int i = 0; i < bundles.size(); i++) { 1962 SavedBundle bundle = (SavedBundle) bundles.get(i); 1963 List categories = bundle.getCategories(); 1964 JMenu catMenu = bundleMenu; 1965 String mainCategory = ""; 1966 for (int catIdx = 0; catIdx < categories.size(); catIdx++) { 1967 String category = (String) categories.get(catIdx); 1968 mainCategory += "." + category; 1969 JMenu tmpMenu = (JMenu) catMenus.get(mainCategory); 1970 if (tmpMenu == null) { 1971 tmpMenu = new JMenu(category); 1972 catMenu.add(tmpMenu); 1973 catMenus.put(mainCategory, tmpMenu); 1974 } 1975 catMenu = tmpMenu; 1976 } 1977 1978 final SavedBundle theBundle = bundle; 1979 mi = new JMenuItem(bundle.getName()); 1980 mi.addActionListener(new ActionListener() { 1981 public void actionPerformed(ActionEvent ae) { 1982 //Do it in a thread 1983 Misc.run(UIManager.this, "processBundle", theBundle); 1984 } 1985 }); 1986 catMenu.add(mi); 1987 } 1988 } 1989 1990 /** 1991 * Overridden to build a custom Window menu. 1992 * @see ucar.unidata.idv.ui.IdvUIManager#makeWindowsMenu(JMenu, IdvWindow) 1993 */ 1994 @Override public void makeWindowsMenu(final JMenu windowMenu, final IdvWindow idvWindow) { 1995 JMenuItem mi; 1996 boolean first = true; 1997 1998 mi = new JMenuItem("Show Data Explorer"); 1999 McVGuiUtils.setMenuImage(mi, Constants.ICON_DATAEXPLORER_SMALL); 2000 mi.addActionListener(this); 2001 mi.setActionCommand(ACT_SHOW_DASHBOARD); 2002 windowMenu.add(mi); 2003 2004 makeTabNavigationMenu(windowMenu); 2005 2006 @SuppressWarnings("unchecked") // it's how the IDV does it. 2007 List windows = new ArrayList(IdvWindow.getWindows()); 2008 for (int i = 0; i < windows.size(); i++) { 2009 final IdvWindow window = (IdvWindow)windows.get(i); 2010 2011 // Skip the main window 2012 if (window.getIsAMainWindow()) { 2013 continue; 2014 } 2015 2016 String title = window.getTitle(); 2017 String titleParts[] = splitTitle(title); 2018 2019 if (titleParts.length == 2) { 2020 title = titleParts[1]; 2021 } 2022 2023 // Skip the data explorer and display controller 2024 String dataSelectorNameParts[] = splitTitle(Constants.DATASELECTOR_NAME); 2025 if (title.equals(Constants.DATASELECTOR_NAME) || title.equals(dataSelectorNameParts[1])) { 2026 continue; 2027 } 2028 2029 // Add a meaningful name if there is none 2030 if (title.isEmpty()) { 2031 title = "<Unnamed>"; 2032 } 2033 2034 if (window.isVisible()) { 2035 mi = new JMenuItem(title); 2036 mi.addActionListener(new ActionListener() { 2037 public void actionPerformed(ActionEvent ae) { 2038 window.toFront(); 2039 } 2040 }); 2041 2042 if (first) { 2043 windowMenu.addSeparator(); 2044 first = false; 2045 } 2046 2047 windowMenu.add(mi); 2048 } 2049 } 2050 Msg.translateTree(windowMenu); 2051 } 2052 2053 /** 2054 * 2055 * @param menu 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(new ActionListener() { 2352 2353 public void actionPerformed(ActionEvent ae) { 2354 if (!inWindow) { 2355 createNewTab(skinid); 2356 } else { 2357 createNewWindow(null, true, 2358 getStateManager().getTitle(), skins.get( 2359 skinIndex).toString(), skins.getRoot( 2360 skinIndex, false), inWindow, null); 2361 } 2362 } 2363 }); 2364 theMenu.add(mi); 2365 } 2366 } 2367 2368 // attach the dynamic skin menu item to the tab menu. 2369// if (!inWindow) { 2370// ((JMenu)newDisplayMenu).addSeparator(); 2371// IdvWindow window = IdvWindow.getActiveWindow(); 2372// 2373// final McvComponentGroup group = 2374// (McvComponentGroup)window.getComponentGroups().get(0); 2375// 2376// JMenuItem mi = new JMenuItem("Choose Your Own Adventure..."); 2377// mi.addActionListener(new ActionListener() { 2378// 2379// public void actionPerformed(ActionEvent e) { 2380// makeDynamicSkin(group); 2381// } 2382// }); 2383// newDisplayMenu.add(mi); 2384// } 2385 } 2386 return newDisplayMenu; 2387 } 2388 2389 // for the time being just create some basic viewmanagers. 2390// public void makeDynamicSkin(McvComponentGroup group) { 2391// // 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...) 2392// try { 2393// Document doc = XmlUtil.getDocument(SKIN_TEMPLATE); 2394// Element root = doc.getDocumentElement(); 2395// Element rightChild = doc.createElement("idv.view"); 2396// rightChild.setAttribute("class", "ucar.unidata.idv.TransectViewManager"); 2397// rightChild.setAttribute("viewid", "viewright1337"); 2398// rightChild.setAttribute("id", "viewright"); 2399// 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%;"); 2400// 2401// Element leftChild = doc.createElement("idv.view"); 2402// leftChild.setAttribute("class", "ucar.unidata.idv.MapViewManager"); 2403// leftChild.setAttribute("viewid", "viewleft1337"); 2404// leftChild.setAttribute("id", "viewleft"); 2405// leftChild.setAttribute("properties", "name=Panel 2;clickToFocus=true;showToolBars=true;shareViews=true;showControlLegend=false;size=300:400;shareGroup=view%versionuid%;"); 2406// 2407// Element startNode = XmlUtil.findElement(root, "splitpane", "embeddednode", "true"); 2408// startNode.appendChild(rightChild); 2409// startNode.appendChild(leftChild); 2410// group.makeDynamicSkin(root); 2411// } catch (Exception e) { 2412// LogUtil.logException("Error: parsing skin template:", e); 2413// } 2414// } 2415// 2416// private static final String SKIN_TEMPLATE = 2417// "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + 2418// "<skin embedded=\"true\">\n" + 2419// " <ui>\n" + 2420// " <panel layout=\"border\" bgcolor=\"red\">\n" + 2421// " <idv.menubar place=\"North\"/>\n" + 2422// " <panel layout=\"border\" place=\"Center\">\n" + 2423// " <panel layout=\"flow\" place=\"North\">\n" + 2424// " <idv.toolbar id=\"idv.toolbar\" place=\"West\"/>\n" + 2425// " <panel id=\"idv.favoritesbar\" place=\"North\"/>\n" + 2426// " </panel>\n" + 2427// " <splitpane embeddednode=\"true\" resizeweight=\"0.5\" onetouchexpandable=\"true\" orientation=\"h\" bgcolor=\"blue\" layout=\"grid\" cols=\"2\" place=\"Center\">\n" + 2428// " </splitpane>\n" + 2429// " </panel>\n" + 2430// " <component idref=\"bottom_bar\"/>\n" + 2431// " </panel>\n" + 2432// " </ui>\n" + 2433// " <styles>\n" + 2434// " <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" + 2435// " <style class=\"textbtn\" space=\"2\" mouse_enter=\"ui.setText(idv.messagelabel,prop:tooltip)\" mouse_exit=\"ui.setText(idv.messagelabel,)\"/>\n" + 2436// " </styles>\n" + 2437// " <components>\n" + 2438// " <idv.statusbar place=\"South\" id=\"bottom_bar\"/>\n" + 2439// " </components>\n" + 2440// " <properties>\n" + 2441// " <property name=\"icon.wait.wait\" value=\"/ucar/unidata/idv/images/wait.gif\"/>\n" + 2442// " </properties>\n" + 2443// "</skin>\n"; 2444 2445 private int holderCount; 2446 2447 /** 2448 * Associates a given ViewManager with a given ComponentHolder. 2449 * 2450 * @param vm The ViewManager that is inside {@code holder}. 2451 * @param holder The ComponentHolder that contains {@code vm}. 2452 */ 2453 public void setViewManagerHolder(ViewManager vm, ComponentHolder holder) { 2454 viewManagers.put(vm, holder); 2455 holderCount = getComponentHolders().size(); 2456 } 2457 2458 public Set<ComponentHolder> getComponentHolders() { 2459 return newHashSet(viewManagers.values()); 2460 } 2461 2462 public int getComponentHolderCount() { 2463 return holderCount; 2464 } 2465 2466 public int getComponentGroupCount() { 2467 return getComponentGroups().size(); 2468 } 2469 2470 /** 2471 * Returns the ComponentHolder containing the given ViewManager. 2472 * 2473 * @param vm The ViewManager whose ComponentHolder is needed. 2474 * 2475 * @return Either null or the ComponentHolder. 2476 */ 2477 public ComponentHolder getViewManagerHolder(ViewManager vm) { 2478 return viewManagers.get(vm); 2479 } 2480 2481 /** 2482 * Disassociate a given ViewManager from its ComponentHolder. 2483 * 2484 * @return The associated 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 * @see ucar.unidata.idv.ui.IdvUIManager#showDashboard() 2496 */ 2497 @Override 2498 public void showDashboard() { 2499 showDashboard(""); 2500 } 2501 2502 /** 2503 * Creates the McVViewPanel component that shows up in the dashboard. 2504 */ 2505 @Override 2506 protected ViewPanel doMakeViewPanel() { 2507 ViewPanel vp = new McIDASVViewPanel(idv); 2508 vp.getContents(); 2509 return vp; 2510 } 2511 2512 /** 2513 * @return A map of skin ids to their index within the skin resource. 2514 */ 2515 private Map<String, Integer> readSkinIds() { 2516 XmlResourceCollection skins = getResourceManager().getXmlResources(IdvResourceManager.RSC_SKIN); 2517 Map<String, Integer> ids = new HashMap<>(skins.size()); 2518 for (int i = 0; i < skins.size(); i++) { 2519 String id = skins.getProperty("skinid", i); 2520 if (id != null) { 2521 ids.put(id, i); 2522 } 2523 } 2524 return ids; 2525 } 2526 2527 /** 2528 * Adds a skinned component holder to the active component group. 2529 * 2530 * @param skinId The value of the skin's skinid attribute. 2531 */ 2532 public void createNewTab(final String skinId) { 2533 IdvWindow activeWindow = IdvWindow.getActiveWindow(); 2534 IdvComponentGroup group = 2535 McVGuiUtils.getComponentGroup(activeWindow); 2536 if (skinIds.containsKey(skinId)) { 2537 group.makeSkin(skinIds.get(skinId)); 2538 } 2539 JFrame frame = activeWindow.getFrame(); 2540 if (frame != null) { 2541 frame.setPreferredSize(frame.getSize()); 2542 } 2543 } 2544 2545 /** 2546 * Method to do the work of showing the Data Explorer (nee Dashboard) 2547 */ 2548 @SuppressWarnings("unchecked") // IdvWindow.getWindows only adds IdvWindows. 2549 public void showDashboard(String tabName) { 2550 if (!initDone) { 2551 return; 2552 } else if (dashboard == null) { 2553 showWaitCursor(); 2554 doMakeBasicWindows(); 2555 showNormalCursor(); 2556 String title = makeTitle(getStateManager().getTitle(), Constants.DATASELECTOR_NAME); 2557 for (IdvWindow window : (List<IdvWindow>)IdvWindow.getWindows()) { 2558 if (title.equals(window.getTitle())) { 2559 dashboard = window; 2560 dashboard.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); 2561 } 2562 } 2563 } else { 2564 dashboard.show(); 2565 } 2566 2567 if (tabName.isEmpty()) { 2568 return; 2569 } 2570 2571 // Dig two panels deep looking for a JTabbedPane 2572 // If you find one, try to show the requested tab name 2573 JComponent contents = dashboard.getContents(); 2574 JComponent component = (JComponent)contents.getComponent(0); 2575 JTabbedPane tPane = null; 2576 if (component instanceof JTabbedPane) { 2577 tPane = (JTabbedPane)component; 2578 } 2579 else { 2580 JComponent component2 = (JComponent)component.getComponent(0); 2581 if (component2 instanceof JTabbedPane) { 2582 tPane = (JTabbedPane)component2; 2583 } 2584 } 2585 if (tPane != null) { 2586 for (int i=0; i<tPane.getTabCount(); i++) { 2587 if (tabName.equals(tPane.getTitleAt(i))) { 2588 tPane.setSelectedIndex(i); 2589 break; 2590 } 2591 } 2592 } 2593 } 2594 2595 /** 2596 * Show the support request form 2597 * 2598 * @param description Default value for the description form entry 2599 * @param stackTrace The stack trace that caused this error. 2600 * @param dialog The dialog to put the gui in, if non-null. 2601 */ 2602 public void showSupportForm(final String description, 2603 final String stackTrace, final JDialog dialog) 2604 { 2605 java.awt.EventQueue.invokeLater(new Runnable() { 2606 public void run() { 2607 // TODO: mcvstatecollector should have a way to gather the 2608 // exception information.. 2609 McIDASV mcv = (McIDASV)getIdv(); 2610 new SupportForm(getStore(), new McvStateCollector(mcv)).setVisible(true); 2611 } 2612 }); 2613 } 2614 2615 /** 2616 * Attempts to locate and display a dashboard component using an ID. 2617 * 2618 * @param id ID of the desired component. 2619 * 2620 * @return True if {@code id} corresponds to a component. False otherwise. 2621 */ 2622 public boolean showDashboardComponent(String id) { 2623 Object comp = findComponent(id); 2624 if (comp != null) { 2625 GuiUtils.showComponentInTabs((JComponent)comp); 2626 return true; 2627 } else { 2628 super.showDashboard(); 2629 for (IdvWindow window : (List<IdvWindow>)IdvWindow.getWindows()) { 2630 String title = makeTitle( 2631 getStateManager().getTitle(), 2632 Constants.DATASELECTOR_NAME 2633 ); 2634 if (title.equals(window.getTitle())) { 2635 dashboard = window; 2636 dashboard.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); 2637 } 2638 } 2639 } 2640 return false; 2641 } 2642 2643 /** 2644 * Close and dispose of the splash window (if it has been created). 2645 */ 2646 @Override 2647 public void splashClose() { 2648 if (splash != null) { 2649 splash.doClose(); 2650 } 2651 } 2652 2653 /** 2654 * Show a message in the splash screen (if it exists) 2655 * 2656 * @param m The message to show 2657 */ 2658 @Override public void splashMsg(String m) { 2659 if (splash != null) { 2660 splash.splashMsg(m); 2661 } 2662 } 2663 2664 /** 2665 * Uses a given toolbar editor to repopulate all toolbars so that they 2666 * correspond to the user's choice of actions. 2667 * 2668 * @param tbe The toolbar editor that contains the actions the user wants. 2669 */ 2670 public void setCurrentToolbars(final McvToolbarEditor tbe) { 2671 List<TwoFacedObject> tfos = tbe.getTLP().getCurrentEntries(); 2672 List<String> buttonIds = new ArrayList<>(tfos.size()); 2673 for (TwoFacedObject tfo : tfos) { 2674 if (McvToolbarEditor.isSpace(tfo)) { 2675 buttonIds.add((String)null); 2676 } else { 2677 buttonIds.add(TwoFacedObject.getIdString(tfo)); 2678 } 2679 } 2680 2681 cachedButtons = buttonIds; 2682 2683 for (JToolBar toolbar : toolbars) { 2684 toolbar.setVisible(false); 2685 populateToolbar(toolbar); 2686 toolbar.setVisible(true); 2687 } 2688 } 2689 2690 /** 2691 * Append a string and object to the buffer 2692 * 2693 * @param sb StringBuffer to append to 2694 * @param name Name of the object 2695 * @param value the object value 2696 */ 2697 private void append(StringBuffer sb, String name, Object value) { 2698 sb.append("<b>").append(name).append("</b>: ").append(value).append("<br>"); 2699 } 2700 2701 private JMenuItem makeControlDescriptorItem(ControlDescriptor cd) { 2702 JMenuItem mi = new JMenuItem(); 2703 if (cd != null) { 2704 mi = new JMenuItem(cd.getLabel()); 2705 mi.addActionListener(new ObjectListener(cd) { 2706 public void actionPerformed(ActionEvent ev) { 2707 idv.doMakeControl(new ArrayList(), 2708 (ControlDescriptor)theObject); 2709 } 2710 }); 2711 } 2712 return mi; 2713 } 2714 2715 /* (non-javadoc) 2716 * Overridden so that the toolbar will update upon saving a bundle. 2717 */ 2718 @Override public void displayTemplatesChanged() { 2719 super.displayTemplatesChanged(); 2720 for (JToolBar toolbar : toolbars) { 2721 toolbar.setVisible(false); 2722 populateToolbar(toolbar); 2723 toolbar.setVisible(true); 2724 } 2725 } 2726 2727 /** 2728 * Called when there has been any change to the favorite bundles and is 2729 * most useful for triggering an update to the {@literal "toolbar bundles"}. 2730 */ 2731 @Override public void favoriteBundlesChanged() { 2732 SwingUtilities.invokeLater(new Runnable() { 2733 public void run() { 2734 for (JToolBar toolbar : toolbars) { 2735 toolbar.setVisible(false); 2736 populateToolbar(toolbar); 2737 toolbar.setVisible(true); 2738 } 2739 } 2740 }); 2741 } 2742 2743 /** 2744 * Show the support request form in a non-swing thread. We do this because we cannot 2745 * call the HttpFormEntry.showUI from a swing thread 2746 * 2747 * @param description Default value for the description form entry 2748 * @param stackTrace The stack trace that caused this error. 2749 * @param dialog The dialog to put the gui in, if non-null. 2750 */ 2751 2752 private void showSupportFormInThread(String description, 2753 String stackTrace, JDialog dialog) { 2754 List<HttpFormEntry> entries = new ArrayList<>(); 2755 2756 StringBuffer extra = new StringBuffer("<h3>McIDAS-V</h3>\n"); 2757 Hashtable<String, String> table = 2758 ((StateManager)getStateManager()).getVersionInfo(); 2759 append(extra, "mcv.version.general", table.get("mcv.version.general")); 2760 append(extra, "mcv.version.build", table.get("mcv.version.build")); 2761 append(extra, "idv.version.general", table.get("idv.version.general")); 2762 append(extra, "idv.version.build", table.get("idv.version.build")); 2763 2764 extra.append("<h3>OS</h3>\n"); 2765 append(extra, "os.name", System.getProperty("os.name")); 2766 append(extra, "os.arch", System.getProperty("os.arch")); 2767 append(extra, "os.version", System.getProperty("os.version")); 2768 2769 extra.append("<h3>Java</h3>\n"); 2770 append(extra, "java.vendor", System.getProperty("java.vendor")); 2771 append(extra, "java.version", System.getProperty("java.version")); 2772 append(extra, "java.home", System.getProperty("java.home")); 2773 2774 StringBuffer javaInfo = new StringBuffer(); 2775 javaInfo.append("Java: home: " + System.getProperty("java.home")); 2776 javaInfo.append(" version: " + System.getProperty("java.version")); 2777 2778 Class c = null; 2779 try { 2780 c = Class.forName("javax.media.j3d.VirtualUniverse"); 2781 Method method = Misc.findMethod(c, "getProperties", 2782 new Class[] {}); 2783 if (method == null) { 2784 javaInfo.append("j3d <1.3"); 2785 } else { 2786 try { 2787 Map m = (Map)method.invoke(c, new Object[] {}); 2788 javaInfo.append(" j3d:" + m.get("j3d.version")); 2789 append(extra, "j3d.version", m.get("j3d.version")); 2790 append(extra, "j3d.vendor", m.get("j3d.vendor")); 2791 append(extra, "j3d.renderer", m.get("j3d.renderer")); 2792 } catch (Exception exc) { 2793 javaInfo.append(" j3d:" + "unknown"); 2794 } 2795 } 2796 } catch (ClassNotFoundException exc) { 2797 append(extra, "j3d", "none"); 2798 } 2799 2800 boolean persistCC = getStore().get("mcv.supportreq.cc", true); 2801 2802 JCheckBox ccMyself = new JCheckBox("Send Copy of Support Request to Me", persistCC); 2803 ccMyself.addActionListener(new ActionListener() { 2804 public void actionPerformed(final ActionEvent e) { 2805 JCheckBox cb = (JCheckBox)e.getSource(); 2806 getStore().put("mcv.supportreq.cc", cb.isSelected()); 2807 } 2808 }); 2809 2810 boolean doWrap = idv.getProperty(PROP_WRAP_SUPPORT_DESC, true); 2811 2812 HttpFormEntry descriptionEntry; 2813 HttpFormEntry nameEntry; 2814 HttpFormEntry emailEntry; 2815 HttpFormEntry orgEntry; 2816 2817 entries.add(nameEntry = new HttpFormEntry(HttpFormEntry.TYPE_INPUT, 2818 "form_data[fromName]", "Name:", 2819 getStore().get(PROP_HELP_NAME, (String) null))); 2820 entries.add(emailEntry = new HttpFormEntry(HttpFormEntry.TYPE_INPUT, 2821 "form_data[email]", "Your Email:", 2822 getStore().get(PROP_HELP_EMAIL, (String) null))); 2823 entries.add(orgEntry = new HttpFormEntry(HttpFormEntry.TYPE_INPUT, 2824 "form_data[organization]", "Organization:", 2825 getStore().get(PROP_HELP_ORG, (String) null))); 2826 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_INPUT, 2827 "form_data[subject]", "Subject:")); 2828 2829 entries.add( 2830 new HttpFormEntry( 2831 HttpFormEntry.TYPE_LABEL, "", 2832 "<html>Please provide a <i>thorough</i> description of the problem you encountered:</html>")); 2833 entries.add(descriptionEntry = 2834 new FormEntry(doWrap, HttpFormEntry.TYPE_AREA, 2835 "form_data[description]", "Description:", 2836 description, 5, 30, true)); 2837 2838 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_FILE, 2839 "form_data[att_two]", "Attachment 1:", "", 2840 false)); 2841 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_FILE, 2842 "form_data[att_three]", "Attachment 2:", "", 2843 false)); 2844 2845 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_HIDDEN, 2846 "form_data[submit]", "", "Send Email")); 2847 2848 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_HIDDEN, 2849 "form_data[p_version]", "", 2850 getStateManager().getVersion() 2851 + " build date:" 2852 + getStateManager().getBuildDate())); 2853 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_HIDDEN, 2854 "form_data[opsys]", "", 2855 System.getProperty("os.name"))); 2856 entries.add(new HttpFormEntry(HttpFormEntry.TYPE_HIDDEN, 2857 "form_data[hardware]", "", 2858 javaInfo.toString())); 2859 2860 JLabel topLabel = 2861 new JLabel("<html>This form allows you to send a support request to the McIDAS Help Desk.<br></html>"); 2862 2863 JCheckBox includeBundleCbx = 2864 new JCheckBox("Include Current State as Bundle", false); 2865 2866 List<JCheckBox> checkboxes = list(includeBundleCbx, ccMyself); 2867 2868 boolean alreadyHaveDialog = true; 2869 if (dialog == null) { 2870 // NOTE: if the dialog is modeless you can leave alreadyHaveDialog 2871 // alone. If the dialog is modal you need to set alreadyHaveDialog 2872 // to false. 2873 // If alreadyHaveDialog is false with a modeless dialog, the later 2874 // call to HttpFormEntry.showUI will return false and break out of 2875 // the while loop without talking to the HTTP server. 2876 dialog = GuiUtils.createDialog(LogUtil.getCurrentWindow(), 2877 "Support Request Form", false); 2878// alreadyHaveDialog = false; 2879 } 2880 2881 JLabel statusLabel = GuiUtils.cLabel(" "); 2882 JComponent bottom = LayoutUtil.vbox(LayoutUtil.leftVbox(checkboxes), statusLabel); 2883 2884 while (true) { 2885 //Show form. Check if user pressed cancel. 2886 statusLabel.setText(" "); 2887 if ( !HttpFormEntry.showUI(entries, LayoutUtil.inset(topLabel, 10), 2888 bottom, dialog, alreadyHaveDialog)) { 2889 break; 2890 } 2891 statusLabel.setText("Posting support request..."); 2892 2893 //Save persistent state 2894 getStore().put(PROP_HELP_NAME, nameEntry.getValue()); 2895 getStore().put(PROP_HELP_ORG, orgEntry.getValue()); 2896 getStore().put(PROP_HELP_EMAIL, emailEntry.getValue()); 2897 getStore().save(); 2898 2899 List<HttpFormEntry> entriesToPost = 2900 new ArrayList<>(entries); 2901 2902 if ((stackTrace != null) && (stackTrace.length() > 0)) { 2903 entriesToPost.remove(descriptionEntry); 2904 String newDescription = 2905 descriptionEntry.getValue() 2906 + "\n\n******************\nStack trace:\n" + stackTrace; 2907 entriesToPost.add( 2908 new HttpFormEntry( 2909 HttpFormEntry.TYPE_HIDDEN, "form_data[description]", 2910 "Description:", newDescription, 5, 30, true)); 2911 } 2912 2913 try { 2914 extra.append(idv.getPluginManager().getPluginHtml()); 2915 extra.append(getResourceManager().getHtmlView()); 2916 2917 entriesToPost.add(new HttpFormEntry("form_data[att_extra]", 2918 "extra.html", extra.toString().getBytes())); 2919 2920 if (includeBundleCbx.isSelected()) { 2921 entriesToPost.add( 2922 new HttpFormEntry( 2923 "form_data[att_state]", "bundle" + Constants.SUFFIX_MCV, 2924 idv.getPersistenceManager().getBundleXml( 2925 true).getBytes())); 2926 } 2927 entriesToPost.add(new HttpFormEntry(HttpFormEntry.TYPE_HIDDEN, 2928 "form_data[cc_user]", "", 2929 Boolean.toString(getStore().get("mcv.supportreq.cc", true)))); 2930 2931 String[] results = 2932 HttpFormEntry.doPost(entriesToPost, SUPPORT_REQ_URL); 2933 2934 if (results[0] != null) { 2935 GuiUtils.showHtmlDialog( 2936 results[0], "Support Request Response - Error", 2937 "Support Request Response - Error", null, true); 2938 continue; 2939 } 2940 String html = results[1]; 2941 if (html.toLowerCase().indexOf("your email has been sent") 2942 >= 0) { 2943 LogUtil.userMessage("Your support request has been sent"); 2944 break; 2945 } else if (html.toLowerCase().indexOf("required fields") 2946 >= 0) { 2947 LogUtil.userErrorMessage( 2948 "<html>There was a problem submitting your request. <br>Is your email correct?</html>"); 2949 } else { 2950 GuiUtils.showHtmlDialog( 2951 html, "Unknown Support Request Response", 2952 "Unknown Support Request Response", null, true); 2953 System.err.println(html.toLowerCase()); 2954 } 2955 } catch (Exception exc) { 2956 LogUtil.logException("Doing support request form", exc); 2957 } 2958 } 2959 dialog.dispose(); 2960 } 2961 2962 @Override protected IdvXmlUi doMakeIdvXmlUi(IdvWindow window, 2963 List viewManagers, Element skinRoot) 2964 { 2965 return new McIDASVXmlUi(window, viewManagers, idv, skinRoot); 2966 } 2967 2968 /** 2969 * DeInitialize the given menu before it is shown 2970 * @see ucar.unidata.idv.ui.IdvUIManager#historyMenuSelected(JMenu) 2971 */ 2972 @Override 2973 protected void handleMenuDeSelected(final String id, final JMenu menu, final IdvWindow idvWindow) { 2974 super.handleMenuDeSelected(id, menu, idvWindow); 2975 } 2976 2977 /** 2978 * Initialize the given menu before it is shown 2979 * @see ucar.unidata.idv.ui.IdvUIManager#historyMenuSelected(JMenu) 2980 */ 2981 @Override 2982 protected void handleMenuSelected(final String id, final JMenu menu, final IdvWindow idvWindow) { 2983 if (id.equals(MENU_NEWVIEWS)) { 2984 ViewManager last = getVMManager().getLastActiveViewManager(); 2985 menu.removeAll(); 2986 makeViewStateMenu(menu, last); 2987 } else if (id.equals("bundles")) { 2988 menu.removeAll(); 2989 makeBundleMenu(menu); 2990 } else if (id.equals(MENU_NEWDISPLAY_TAB)) { 2991 menu.removeAll(); 2992 doMakeNewDisplayMenu(menu, false); 2993 } else if (id.equals(MENU_NEWDISPLAY)) { 2994 menu.removeAll(); 2995 doMakeNewDisplayMenu(menu, true); 2996 } else if (id.equals("menu.tools.projections.deletesaved")) { 2997 menu.removeAll(); 2998 makeDeleteViewsMenu(menu); 2999 } else if (id.equals("file.default.layout")) { 3000 makeDefaultLayoutMenu(menu); 3001 } else if (id.equals("tools.formulas")) { 3002 menu.removeAll(); 3003 makeFormulasMenu(menu); 3004 } else { 3005 super.handleMenuSelected(id, menu, idvWindow); 3006 } 3007 } 3008 3009 /** A cache of the operand name to value for the user choices */ 3010 private Map operandCache; 3011 3012 @Override public List selectUserChoices(String msg, List userOperands) { 3013 if (operandCache == null) { 3014 operandCache = 3015 (Hashtable) getStore().getEncodedFile("operandcache.xml"); 3016 if (operandCache == null) { 3017 operandCache = new Hashtable(); 3018 } 3019 } 3020 List fields = new ArrayList(); 3021 List components = new ArrayList(); 3022 List persistentCbxs = new ArrayList(); 3023 components.add(new JLabel("Property")); 3024 components.add(new JLabel("Value")); 3025 components.add(new JLabel("Save in Bundle")); 3026 for (int i = 0; i < userOperands.size(); i++) { 3027 DataOperand operand = (DataOperand) userOperands.get(i); 3028 String fieldType = (String) operand.getProperty("type"); 3029 if (fieldType == null) { 3030 fieldType = FIELDTYPE_TEXT; 3031 } 3032 DerivedDataChoice formula = operand.getDataChoice(); 3033 String description = operand.getDescription(); 3034 if (formula != null) { 3035 description = formula.toString(); 3036 } 3037 3038 String label = operand.getLabel(); 3039 Object dflt = operand.getUserDefault(); 3040 Object cacheKeyNewStyle = Misc.newList(description, label, fieldType); 3041 Object cacheKey = Misc.newList(label, fieldType); 3042 3043 Object cachedOperand = null; 3044 boolean oldStyle = operandCache.containsKey(cacheKey); 3045 boolean newStyle = operandCache.containsKey(cacheKeyNewStyle); 3046 3047 // if new style, always use that and ignore old style 3048 // if no new style, proceed as before. 3049 if (newStyle) { 3050 cachedOperand = operandCache.get(cacheKeyNewStyle); 3051 } else if (oldStyle) { 3052 cachedOperand = operandCache.get(cacheKey); 3053 } 3054 3055 if (cachedOperand != null) { 3056 dflt = cachedOperand; 3057 } 3058 3059 JCheckBox cbx = new JCheckBox("", operand.isPersistent()); 3060 persistentCbxs.add(cbx); 3061 JComponent field = null; 3062 JComponent fieldComp = null; 3063 if (fieldType.equals(FIELDTYPE_TEXT)) { 3064 String rowString = (String) operand.getProperty("rows"); 3065 if (rowString == null) { 3066 rowString = "1"; 3067 } 3068 int rows = new Integer(rowString).intValue(); 3069 if (rows == 1) { 3070 field = new JTextField((dflt != null) 3071 ? dflt.toString() 3072 : "", 15); 3073 } else { 3074 field = new JTextArea((dflt != null) 3075 ? dflt.toString() 3076 : "", rows, 15); 3077 fieldComp = GuiUtils.makeScrollPane(field, 200, 100); 3078 } 3079 } else if (fieldType.equals(FIELDTYPE_BOOLEAN)) { 3080 field = new JCheckBox("", ((dflt != null) 3081 ? new Boolean( 3082 dflt.toString()).booleanValue() 3083 : true)); 3084 } else if (fieldType.equals(FIELDTYPE_CHOICE)) { 3085 String choices = (String) operand.getProperty("choices"); 3086 if (choices == null) { 3087 throw new IllegalArgumentException( 3088 "No 'choices' attribute defined for operand: " 3089 + operand); 3090 } 3091 List l = StringUtil.split(choices, ";", true, true); 3092 field = new JComboBox(new Vector(l)); 3093 if ((dflt != null) && l.contains(dflt)) { 3094 ((JComboBox) field).setSelectedItem(dflt); 3095 } 3096 } else if (fieldType.equals(FIELDTYPE_FILE)) { 3097 JTextField fileFld = new JTextField(((dflt != null) 3098 ? dflt.toString() 3099 : ""), 30); 3100 field = fileFld; 3101 String patterns = operand.getProperty("filepattern"); 3102 List filters = null; 3103 if (patterns != null) { 3104 filters = new ArrayList(); 3105 List toks = StringUtil.split(patterns, ";", true, true); 3106 for (int tokIdx = 0; tokIdx < toks.size(); tokIdx++) { 3107 String tok = (String) toks.get(tokIdx); 3108 List subToks = StringUtil.split(tok, ":", true, true); 3109 if (subToks.size() == 2) { 3110 filters.add( 3111 new PatternFileFilter( 3112 (String) subToks.get(0), 3113 (String) subToks.get(1))); 3114 } else { 3115 filters.add(new PatternFileFilter(tok, tok)); 3116 } 3117 } 3118 } 3119 fieldComp = GuiUtils.centerRight(GuiUtils.hfill(fileFld), 3120 GuiUtils.makeFileBrowseButton(fileFld, filters)); 3121 } else if (fieldType.equals(FIELDTYPE_LOCATION)) { 3122 List l = ((dflt != null) 3123 ? StringUtil.split(dflt.toString(), ";", true, true) 3124 : (List) new ArrayList()); 3125 final LatLonWidget llw = new LatLonWidget(); 3126 field = llw; 3127 if (l.size() == 2) { 3128 llw.setLat(Misc.decodeLatLon(l.get(0).toString())); 3129 llw.setLon(Misc.decodeLatLon(l.get(1).toString())); 3130 } 3131 final JButton centerPopupBtn = 3132 GuiUtils.getImageButton("/auxdata/ui/icons/Map16.gif", 3133 getClass()); 3134 centerPopupBtn.setToolTipText("Center on current displays"); 3135 centerPopupBtn.addActionListener(new ActionListener() { 3136 public void actionPerformed(ActionEvent ae) { 3137 popupCenterMenu(centerPopupBtn, llw); 3138 } 3139 }); 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 = (String) 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(new ActionListener() { 3226 public void actionPerformed(final ActionEvent e) { 3227 ((McIDASV)idv).doSaveAsDefaultLayout(); 3228 } 3229 }); 3230 3231 JMenuItem removeLayout = new JMenuItem("Remove"); 3232 McVGuiUtils.setMenuImage(removeLayout, Constants.ICON_DEFAULTLAYOUTDELETE_SMALL); 3233 removeLayout.setToolTipText("Remove saved default layout"); 3234 removeLayout.addActionListener(new ActionListener() { 3235 public void actionPerformed(final ActionEvent e) { 3236 idv.doClearDefaults(); 3237 } 3238 }); 3239 3240 removeLayout.setEnabled(((McIDASV)idv).hasDefaultLayout()); 3241 3242 menu.add(saveLayout); 3243 menu.add(removeLayout); 3244 } 3245 3246 /** 3247 * Bundles any compatible {@link ViewManager} states into {@link JMenuItem}s 3248 * and adds said {@code JMenuItem}s to {@code menu}. Incompatible states are 3249 * ignored. 3250 * 3251 * <p>Each {@code JMenuItem} (except those under the {@literal "Delete"} menu--apologies) 3252 * associates a {@literal "view state"} and an {@link ObjectListener}. 3253 * The {@code ObjectListener} uses this associated view state to attempt reinitialization 3254 * of {@code vm}. 3255 * 3256 * <p>Override reasoning: 3257 * <ul> 3258 * <li>terminology ({@literal "views"} rather than {@literal "viewpoints"}).</li> 3259 * <li> 3260 * use of {@link #filterVMMStatesWithVM(ViewManager, Collection)} to 3261 * properly detect the {@literal "no saved views"} case. 3262 * </li> 3263 * </ul> 3264 * 3265 * @param menu Menu to populate. Should not be {@code null}. 3266 * @param vm {@code ViewManager} that might get reinitialized. Should not be {@code null}. 3267 * 3268 * @see ViewManager#initWith(ViewManager, boolean) 3269 * @see ViewManager#initWith(ViewState) 3270 * @see IdvUIManager#makeViewStateMenu(JMenu, ViewManager) 3271 */ 3272 @Override public void makeViewStateMenu(final JMenu menu, final ViewManager vm) { 3273 List<TwoFacedObject> vmStates = filterVMMStatesWithVM(vm, getVMManager().getVMState()); 3274 if (vmStates.isEmpty()) { 3275 JMenuItem item = new JMenuItem(Msg.msg("No Saved Views")); 3276 item.setEnabled(false); 3277 menu.add(item); 3278 } else { 3279 JMenu deleteMenu = new JMenu("Delete"); 3280 makeDeleteViewsMenu(deleteMenu); 3281 menu.add(deleteMenu); 3282 } 3283 3284 for (TwoFacedObject tfo : vmStates) { 3285 JMenuItem mi = new JMenuItem(tfo.getLabel().toString()); 3286 menu.add(mi); 3287 mi.addActionListener(new ObjectListener(tfo.getId()) { 3288 public void actionPerformed(final ActionEvent e) { 3289 if (vm == null) { 3290 return; 3291 } 3292 3293 if (theObject instanceof ViewManager) { 3294 vm.initWith((ViewManager)theObject, true); 3295 } else if (theObject instanceof ViewState) { 3296 try { 3297 vm.initWith((ViewState)theObject); 3298 } catch (Throwable ex) { 3299 logException("Initializing view with ViewState", ex); 3300 } 3301 } else { 3302 LogUtil.consoleMessage("UIManager.makeViewStateMenu: Object of unknown type: "+theObject.getClass().getName()); 3303 } 3304 } 3305 }); 3306 } 3307 } 3308 3309 /** 3310 * Returns a list of {@link TwoFacedObject}s that are known to be 3311 * compatible with {@code vm}. 3312 * 3313 * <p>This method is currently capable of dealing with {@code TwoFacedObject}s and 3314 * {@link ViewState}s within {@code states}. Any other types are ignored. 3315 * 3316 * @param vm {@link ViewManager} to use for compatibility tests. {@code null} is allowed. 3317 * @param states Collection of objects to test against {@code vm}. {@code null} is allowed. 3318 * 3319 * @return Either a {@link List} of compatible {@literal "view states"} or an empty {@code List}. 3320 * 3321 * @see ViewManager#isCompatibleWith(ViewManager) 3322 * @see ViewManager#isCompatibleWith(ViewState) 3323 * @see #makeViewStateMenu(JMenu, ViewManager) 3324 */ 3325 public static List<TwoFacedObject> filterVMMStatesWithVM(final ViewManager vm, final Collection<?> states) { 3326 if ((vm == null) || (states == null) || states.isEmpty()) { 3327 return Collections.emptyList(); 3328 } 3329 3330 List<TwoFacedObject> validStates = new ArrayList<>(states.size()); 3331 for (Object obj : states) { 3332 TwoFacedObject tfo = null; 3333 if (obj instanceof TwoFacedObject) { 3334 tfo = (TwoFacedObject)obj; 3335 if (vm.isCompatibleWith((ViewManager)tfo.getId())) { 3336 continue; 3337 } 3338 } else if (obj instanceof ViewState) { 3339 if (!vm.isCompatibleWith((ViewState)obj)) { 3340 continue; 3341 } 3342 tfo = new TwoFacedObject(((ViewState)obj).getName(), obj); 3343 } else { 3344 LogUtil.consoleMessage("UIManager.filterVMMStatesWithVM: Object of unknown type: "+obj.getClass().getName()); 3345 continue; 3346 } 3347 validStates.add(tfo); 3348 } 3349 return validStates; 3350 } 3351 3352 /** 3353 * Overridden to build a custom Display menu. 3354 * @see ucar.unidata.idv.ui.IdvUIManager#initializeDisplayMenu(JMenu) 3355 */ 3356 @Override protected void initializeDisplayMenu(JMenu displayMenu) { 3357 JMenu m; 3358 JMenuItem mi; 3359 3360 // Get the list of possible standalone control descriptors 3361 Hashtable controlsHash = new Hashtable(); 3362 List controlDescriptors = getStandAloneControlDescriptors(); 3363 for (int i = 0; i < controlDescriptors.size(); i++) { 3364 ControlDescriptor cd = (ControlDescriptor)controlDescriptors.get(i); 3365 String cdLabel = cd.getLabel(); 3366 if (cdLabel.equals("Range Rings")) { 3367 controlsHash.put(cdLabel, cd); 3368 } else if (cdLabel.equals("Range and Bearing")) { 3369 controlsHash.put(cdLabel, cd); 3370 } else if (cdLabel.equals("Location Indicator")) { 3371 controlsHash.put(cdLabel, cd); 3372 } else if (cdLabel.equals("Drawing Control")) { 3373 controlsHash.put(cdLabel, cd); 3374 } else if (cdLabel.equals("Transect Drawing Control")) { 3375 controlsHash.put(cdLabel, cd); 3376 } 3377 } 3378 3379 // Build the menu 3380 ControlDescriptor cd; 3381 3382 mi = new JMenuItem("Create Layer from Data Source..."); 3383 mi.addActionListener(new ActionListener() { 3384 @Override public void actionPerformed(ActionEvent ae) { 3385 showDashboard("Data Sources"); 3386 } 3387 }); 3388 displayMenu.add(mi); 3389 3390 mi = new JMenuItem("Layer Controls..."); 3391 mi.addActionListener(new ActionListener() { 3392 @Override public void actionPerformed(ActionEvent ae) { 3393 showDashboard("Layer Controls"); 3394 } 3395 }); 3396 displayMenu.add(mi); 3397 3398 displayMenu.addSeparator(); 3399 3400 cd = (ControlDescriptor)controlsHash.get("Range Rings"); 3401 mi = makeControlDescriptorItem(cd); 3402 mi.setText("Add Range Rings"); 3403 displayMenu.add(mi); 3404 3405 cd = (ControlDescriptor)controlsHash.get("Range and Bearing"); 3406 mi = makeControlDescriptorItem(cd); 3407 McVGuiUtils.setMenuImage(mi, Constants.ICON_RANGEANDBEARING_SMALL); 3408 mi.setText("Add Range and Bearing"); 3409 displayMenu.add(mi); 3410 3411 displayMenu.addSeparator(); 3412 3413 cd = (ControlDescriptor)controlsHash.get("Transect Drawing Control"); 3414 mi = makeControlDescriptorItem(cd); 3415 mi.setText("Draw Transect..."); 3416 displayMenu.add(mi); 3417 3418 cd = (ControlDescriptor)controlsHash.get("Drawing Control"); 3419 mi = makeControlDescriptorItem(cd); 3420 mi.setText("Draw Freely..."); 3421 displayMenu.add(mi); 3422 3423 displayMenu.addSeparator(); 3424 3425 cd = (ControlDescriptor)controlsHash.get("Location Indicator"); 3426 mi = makeControlDescriptorItem(cd); 3427 McVGuiUtils.setMenuImage(mi, Constants.ICON_LOCATION_SMALL); 3428 mi.setText("Add Location Indicator"); 3429 displayMenu.add(mi); 3430 3431 ControlDescriptor locationDescriptor = idv.getControlDescriptor("locationcontrol"); 3432 if (locationDescriptor != null) { 3433 List stations = idv.getLocationList(); 3434 ObjectListener listener = new ObjectListener(locationDescriptor) { 3435 public void actionPerformed(ActionEvent ae, Object obj) { 3436 addStationDisplay((NamedStationTable) obj, (ControlDescriptor) theObject); 3437 } 3438 }; 3439 List menuItems = NamedStationTable.makeMenuItems(stations, listener); 3440 displayMenu.add(MenuUtil.makeMenu("Plot Location Labels", menuItems)); 3441 } 3442 3443 displayMenu.addSeparator(); 3444 3445 mi = new JMenuItem("Add Background Image"); 3446 McVGuiUtils.setMenuImage(mi, Constants.ICON_BACKGROUND_SMALL); 3447 mi.addActionListener(new ActionListener() { 3448 public void actionPerformed(ActionEvent ae) { 3449 getIdv().doMakeBackgroundImage(); 3450 } 3451 }); 3452 displayMenu.add(mi); 3453 3454 mi = new JMenuItem("Reset Map Layer to Defaults"); 3455 mi.addActionListener(new ActionListener() { 3456 public void actionPerformed(ActionEvent ae) { 3457 // TODO: Call IdvUIManager.addDefaultMap()... should be made private 3458// addDefaultMap(); 3459 ControlDescriptor mapDescriptor = idv.getControlDescriptor("mapdisplay"); 3460 if (mapDescriptor == null) { 3461 return; 3462 } 3463 String attrs = "initializeAsDefault=true;displayName=Default Background Maps;"; 3464 idv.doMakeControl(new ArrayList(), mapDescriptor, attrs, null); 3465 } 3466 }); 3467 displayMenu.add(mi); 3468 Msg.translateTree(displayMenu); 3469 } 3470 3471 /** 3472 * Get the window title from the skin 3473 * 3474 * @param index the skin index 3475 * 3476 * @return the title 3477 */ 3478 private String getWindowTitleFromSkin(final int index) { 3479 if (!skinToTitle.containsKey(index)) { 3480 IdvResourceManager mngr = getResourceManager(); 3481 XmlResourceCollection skins = mngr.getXmlResources(mngr.RSC_SKIN); 3482 List<String> names = StringUtil.split(skins.getShortName(index), ">", true, true); 3483 String title = getStateManager().getTitle(); 3484 if (!names.isEmpty()) { 3485 title = title + " - " + StringUtil.join(" - ", names); 3486 } 3487 skinToTitle.put(index, title); 3488 } 3489 return skinToTitle.get(index); 3490 } 3491 3492 @SuppressWarnings("unchecked") 3493 @Override public Hashtable getMenuIds() { 3494 return menuIds; 3495 } 3496 3497 @SuppressWarnings("unchecked") 3498 @Override public JMenuBar doMakeMenuBar(final IdvWindow idvWindow) { 3499 Hashtable<String, JMenuItem> menuMap = new Hashtable<>(); 3500 JMenuBar menuBar = new JMenuBar(); 3501 final IdvResourceManager mngr = getResourceManager(); 3502 XmlResourceCollection xrc = mngr.getXmlResources(mngr.RSC_MENUBAR); 3503 Hashtable<String, ImageIcon> actionIcons = new Hashtable<>(); 3504 3505 for (int i = 0; i < xrc.size(); i++) { 3506 GuiUtils.processXmlMenuBar(xrc.getRoot(i), menuBar, getIdv(), menuMap, actionIcons); 3507 } 3508 3509 menuIds = new Hashtable<>(menuMap); 3510 3511 // Ensure that the "help" menu is the last menu. 3512 JMenuItem helpMenu = menuMap.get(MENU_HELP); 3513 if (helpMenu != null) { 3514 menuBar.remove(helpMenu); 3515 menuBar.add(helpMenu); 3516 } 3517 3518 //TODO: Perhaps we will put the different skins in the menu? 3519 JMenu newDisplayMenu = (JMenu)menuMap.get(MENU_NEWDISPLAY); 3520 if (newDisplayMenu != null) { 3521 MenuUtil.makeMenu(newDisplayMenu, makeSkinMenuItems(makeMenuBarActionListener(), true, false)); 3522 } 3523 3524// final JMenu publishMenu = menuMap.get(MENU_PUBLISH); 3525// if (publishMenu != null) { 3526// if (!getPublishManager().isPublishingEnabled()) 3527// publishMenu.getParent().remove(publishMenu); 3528// else 3529// getPublishManager().initMenu(publishMenu); 3530// } 3531 3532 for (Entry<String, JMenuItem> e : menuMap.entrySet()) { 3533 if (!(e.getValue() instanceof JMenu)) { 3534 continue; 3535 } 3536 String menuId = e.getKey(); 3537 JMenu menu = (JMenu)e.getValue(); 3538 menu.addMenuListener(makeMenuBarListener(menuId, menu, idvWindow)); 3539 } 3540 return menuBar; 3541 } 3542 3543 private final ActionListener makeMenuBarActionListener() { 3544 final IdvResourceManager mngr = getResourceManager(); 3545 return new ActionListener() { 3546 public void actionPerformed(final ActionEvent 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 3556 private final MenuListener makeMenuBarListener(final String id, final JMenu menu, final IdvWindow idvWindow) { 3557 return new MenuListener() { 3558 public void menuCanceled(final MenuEvent e) { } 3559 public void menuDeselected(final MenuEvent e) { handleMenuDeSelected(id, menu, idvWindow); } 3560 public void menuSelected(final MenuEvent e) { handleMenuSelected(id, menu, idvWindow); } 3561 }; 3562 } 3563 3564 /** 3565 * Handle mouse clicks that occur within the toolbar. 3566 */ 3567 private class PopupListener extends MouseAdapter { 3568 3569 private JPopupMenu popup; 3570 3571 public PopupListener(JPopupMenu p) { 3572 popup = p; 3573 } 3574 3575 // handle right clicks on os x and linux 3576 public void mousePressed(MouseEvent e) { 3577 if (e.isPopupTrigger()) { 3578 popup.show(e.getComponent(), e.getX(), e.getY()); 3579 } 3580 } 3581 3582 // Windows doesn't seem to trigger mousePressed() for right clicks, but 3583 // never fear; mouseReleased() does the job. 3584 public void mouseReleased(MouseEvent e) { 3585 if (e.isPopupTrigger()) { 3586 popup.show(e.getComponent(), e.getX(), e.getY()); 3587 } 3588 } 3589 } 3590 3591 /** 3592 * Handle (polymorphically) the {@link ucar.unidata.idv.ui.DataControlDialog}. 3593 * This dialog is used to either select a display control to create 3594 * or is used to set the timers used for a {@link ucar.unidata.data.DataSource}. 3595 * 3596 * @param dcd The dialog 3597 */ 3598 public void processDialog(DataControlDialog dcd) { 3599 int estimatedMB = getEstimatedMegabytes(dcd); 3600 if (estimatedMB > 0) { 3601 double totalMem = Runtime.getRuntime().maxMemory(); 3602 double highMem = Runtime.getRuntime().totalMemory(); 3603 double freeMem = Runtime.getRuntime().freeMemory(); 3604 double usedMem = (highMem - freeMem); 3605 int availableMB = Math.round( ((float)totalMem - (float)usedMem) / 1024f / 1024f); 3606 int percentOfAvailable = Math.round((float)estimatedMB / (float)availableMB * 100f); 3607 if (percentOfAvailable > 95) { 3608 String message = "<html>You are attempting to load " + estimatedMB + "MB of data,<br>"; 3609 message += "which exceeds 95% of total amount available (" + availableMB +"MB).<br>"; 3610 message += "Data load cancelled.</html>"; 3611 JComponent msgLabel = new JLabel(message); 3612 GuiUtils.showDialog("Data Size", msgLabel); 3613 return; 3614 } else if (percentOfAvailable >= 75) { 3615 String message = "<html>You are attempting to load " + estimatedMB + "MB of data,<br>"; 3616 message += percentOfAvailable + "% of the total amount available (" + availableMB + "MB).<br>"; 3617 message += "Continue loading data?</html>"; 3618 JComponent msgLabel = new JLabel(message); 3619 if (!GuiUtils.askOkCancel("Data Size", msgLabel)) { 3620 return; 3621 } 3622 } 3623 } 3624 super.processDialog(dcd); 3625 } 3626 3627 /** 3628 * Estimate the number of megabytes that will be used by this data selection 3629 */ 3630 protected int getEstimatedMegabytes(DataControlDialog dcd) { 3631 int estimatedMB = 0; 3632 DataChoice dataChoice = dcd.getDataChoice(); 3633 if (dataChoice != null) { 3634 Object[] selectedControls = dcd.getSelectedControls(); 3635 for (int i = 0; i < selectedControls.length; i++) { 3636 ControlDescriptor cd = (ControlDescriptor) selectedControls[i]; 3637 3638 //Check if the data selection is ok 3639 if(!dcd.getDataSelectionWidget().okToCreateTheDisplay(cd.doesLevels())) { 3640 continue; 3641 } 3642 3643 DataSelection dataSelection = dcd.getDataSelectionWidget().createDataSelection(cd.doesLevels()); 3644 3645 // Get the size in pixels of the requested image 3646 Object gotSize = dataSelection.getProperty("SIZE"); 3647 if (gotSize == null) { 3648 continue; 3649 } 3650 List<String> dims = StringUtil.split((String)gotSize, " ", false, false); 3651 int myLines = -1; 3652 int myElements = -1; 3653 if (dims.size() == 2) { 3654 try { 3655 myLines = Integer.parseInt(dims.get(0)); 3656 myElements = Integer.parseInt(dims.get(1)); 3657 } 3658 catch (Exception e) { } 3659 } 3660 3661 // Get the count of times requested 3662 int timeCount = 1; 3663 DataSelectionWidget dsw = dcd.getDataSelectionWidget(); 3664 List times = dsw.getSelectedDateTimes(); 3665 List timesAll = dsw.getAllDateTimes(); 3666 if ((times != null) && !times.isEmpty()) { 3667 timeCount = times.size(); 3668 } else if ((timesAll != null) && !timesAll.isEmpty()) { 3669 timeCount = timesAll.size(); 3670 } 3671 3672 // Total number of pixels 3673 // Assumed lines x elements x times x 4bytes 3674 // Empirically seems to be taking *twice* that (64bit fields??) 3675 float totalPixels = (float)myLines * (float)myElements * (float)timeCount; 3676 float totalBytes = totalPixels * 4 * 2; 3677 estimatedMB += Math.round(totalBytes / 1024f / 1024f); 3678 3679 int additionalMB = 0; 3680 // Empirical tests show that textures are not affecting 3681 // required memory... comment out for now 3682 /* 3683 int textureDimensions = 2048; 3684 int mbPerTexture = Math.round((float)textureDimensions * (float)textureDimensions * 4 / 1024f / 1024f); 3685 int textureCount = (int)Math.ceil((float)myLines / 2048f) * (int)Math.ceil((float)myElements / 2048f); 3686 int additionalMB = textureCount * mbPerTexture * timeCount; 3687 */ 3688 estimatedMB += additionalMB; 3689 } 3690 } 3691 return estimatedMB; 3692 } 3693 3694 /** 3695 * Represents a SavedBundle as a tree. 3696 */ 3697 private class BundleTreeNode { 3698 3699 private String name; 3700 3701 private SavedBundle bundle; 3702 3703 private List<BundleTreeNode> kids; 3704 3705 /** 3706 * This constructor is used to build a node that is considered a 3707 * "parent." These nodes only have child nodes, no SavedBundles. This 3708 * was done so that distinguishing between bundles and bundle 3709 * subcategories would be easy. 3710 * 3711 * @param name The name of this node. For a parent node with 3712 * "Toolbar>cat" as the path, the name parameter would contain 3713 * only "cat." 3714 */ 3715 public BundleTreeNode(String name) { 3716 this(name, null); 3717 } 3718 3719 /** 3720 * Nodes constructed using this constructor can only ever be child 3721 * nodes. 3722 * 3723 * @param name The name of the SavedBundle. 3724 * @param bundle A reference to the SavedBundle. 3725 */ 3726 public BundleTreeNode(String name, SavedBundle bundle) { 3727 this.name = name; 3728 this.bundle = bundle; 3729 kids = new LinkedList<>(); 3730 } 3731 3732 /** 3733 * @param child The node to be added to the current node. 3734 */ 3735 public void addChild(BundleTreeNode child) { 3736 kids.add(child); 3737 } 3738 3739 /** 3740 * @return Returns all child nodes of this node. 3741 */ 3742 public List<BundleTreeNode> getChildren() { 3743 return kids; 3744 } 3745 3746 /** 3747 * @return Return the SavedBundle associated with this node (if any). 3748 */ 3749 public SavedBundle getBundle() { 3750 return bundle; 3751 } 3752 3753 /** 3754 * @return The name of this node. 3755 */ 3756 public String getName() { 3757 return name; 3758 } 3759 } 3760 3761 /** 3762 * A type of {@code HttpFormEntry} that supports line wrapping for 3763 * text area entries. 3764 * 3765 * @see HttpFormEntry 3766 */ 3767 private static class FormEntry extends HttpFormEntry { 3768 /** Initial contents of this entry. */ 3769 private String value = ""; 3770 3771 /** Whether or not the JTextArea should wrap lines. */ 3772 private boolean wrap = true; 3773 3774 /** Entry type. Used to remain compatible with the IDV. */ 3775 private int type = HttpFormEntry.TYPE_AREA; 3776 3777 /** Number of rows in the JTextArea. */ 3778 private int rows = 5; 3779 3780 /** Number of columns in the JTextArea. */ 3781 private int cols = 30; 3782 3783 /** GUI representation of this entry. */ 3784 private JTextArea component = new JTextArea(value, rows, cols); 3785 3786 /** 3787 * Required to keep Java happy. 3788 */ 3789 public FormEntry() { 3790 super(HttpFormEntry.TYPE_AREA, "form_data[description]", 3791 "Description:"); 3792 } 3793 3794 /** 3795 * Using this constructor allows McIDAS-V to control whether or not a 3796 * HttpFormEntry performs line wrapping for JTextArea components. 3797 * 3798 * @see HttpFormEntry#HttpFormEntry(int, String, String, String, int, int, boolean) 3799 */ 3800 public FormEntry(boolean wrap, int type, String name, String label, String value, int rows, int cols, boolean required) { 3801 super(type, name, label, value, rows, cols, required); 3802 this.type = type; 3803 this.rows = rows; 3804 this.cols = cols; 3805 this.wrap = wrap; 3806 } 3807 3808 /** 3809 * Overrides the IDV method so that the McIDAS-V support request form 3810 * will wrap lines in the "Description" field. 3811 */ 3812 @SuppressWarnings("unchecked") 3813 @Override public void addToGui(List guiComps) { 3814 if (type == HttpFormEntry.TYPE_AREA) { 3815 Dimension minSize = new Dimension(500, 200); 3816 guiComps.add(LayoutUtil.top(GuiUtils.rLabel(getLabel()))); 3817 component.setLineWrap(wrap); 3818 component.setWrapStyleWord(wrap); 3819 JScrollPane sp = new JScrollPane(component); 3820 sp.setPreferredSize(minSize); 3821 sp.setMinimumSize(minSize); 3822 guiComps.add(sp); 3823 } else { 3824 super.addToGui(guiComps); 3825 } 3826 } 3827 3828 /** 3829 * Since the IDV doesn't provide a getComponent for 3830 * {@code addToGui}, we must make our {@code component} field 3831 * local to this class. 3832 * Hijacks any value requests so that the local {@code component} 3833 * field is queried, not the IDV's. 3834 */ 3835 @Override public String getValue() { 3836 if (type != HttpFormEntry.TYPE_AREA) { 3837 return super.getValue(); 3838 } 3839 return component.getText(); 3840 } 3841 3842 /** 3843 * Hijacks any requests to set the {@code component} field's text. 3844 */ 3845 @Override public void setValue(final String newValue) { 3846 if (type == HttpFormEntry.TYPE_AREA) { 3847 component.setText(newValue); 3848 } else { 3849 super.setValue(newValue); 3850 } 3851 } 3852 } 3853 3854 /** 3855 * A {@code ToolbarStyle} is a representation of the way icons associated 3856 * with current toolbar actions should be displayed. This notion is so far 3857 * limited to the sizing of icons, but that may change. 3858 */ 3859 public enum ToolbarStyle { 3860 /** 3861 * Represents the current toolbar actions as large icons. Currently, 3862 * {@literal "large"} is defined as {@code 32 x 32} pixels. 3863 */ 3864 LARGE("Large Icons", "action.icons.large", 32), 3865 3866 /** 3867 * Represents the current toolbar actions as medium icons. Currently, 3868 * {@literal "medium"} is defined as {@code 22 x 22} pixels. 3869 */ 3870 MEDIUM("Medium Icons", "action.icons.medium", 22), 3871 3872 /** 3873 * Represents the current toolbar actions as small icons. Currently, 3874 * {@literal "small"} is defined as {@code 16 x 16} pixels. 3875 */ 3876 SMALL("Small Icons", "action.icons.small", 16); 3877 3878 /** Label to use in the toolbar customization popup menu. */ 3879 private final String label; 3880 3881 /** Signals that the user selected a specific icon size. */ 3882 private final String action; 3883 3884 /** Icon dimensions. Each icon should be {@code size * size}. */ 3885 private final int size; 3886 3887 /** 3888 * {@link #size} in {@link String} form, merely for use with the IDV's 3889 * preference functionality. 3890 */ 3891 private final String sizeAsString; 3892 3893 /** 3894 * Initializes a toolbar style. 3895 * 3896 * @param label Label used in the toolbar popup menu. 3897 * @param action Command that signals the user selected this toolbar 3898 * style. 3899 * @param size Dimensions of the icons. 3900 * 3901 * @throws NullPointerException if {@code label} or {@code action} are 3902 * null. 3903 * 3904 * @throws IllegalArgumentException if {@code size} is not positive. 3905 */ 3906 ToolbarStyle(final String label, final String action, final int size) { 3907 requireNonNull(label, "Label cannot be null."); 3908 requireNonNull(action, "Action cannot be null."); 3909 3910 if (size <= 0) { 3911 throw new IllegalArgumentException("Size must be a positive integer"); 3912 } 3913 3914 this.label = label; 3915 this.action = action; 3916 this.size = size; 3917 this.sizeAsString = Integer.toString(size); 3918 } 3919 3920 /** 3921 * Returns the label to use as a brief description of this style. 3922 */ 3923 public String getLabel() { 3924 return label; 3925 } 3926 3927 /** 3928 * Returns the action command associated with this style. 3929 */ 3930 public String getAction() { 3931 return action; 3932 } 3933 3934 /** 3935 * Returns the dimensions of icons used in this style. 3936 */ 3937 public int getSize() { 3938 return size; 3939 } 3940 3941 /** 3942 * Returns {@link #size} as a {@link String} to make cooperating with 3943 * the IDV preferences code easier. 3944 */ 3945 public String getSizeAsString() { 3946 return sizeAsString; 3947 } 3948 3949 /** 3950 * Returns a brief description of this ToolbarStyle. A typical 3951 * example:<br/> 3952 * {@code [ToolbarStyle@1337: label="Large Icons", size=32]} 3953 * 3954 * <p>Note that the format and details provided are subject to change. 3955 */ 3956 public String toString() { 3957 return String.format("[ToolbarStyle@%x: label=%s, size=%d]", 3958 hashCode(), label, size); 3959 } 3960 3961 /** 3962 * Convenience method for build the toolbar customization popup menu. 3963 * 3964 * @param manager {@link UIManager} that will be listening for action 3965 * commands. 3966 * 3967 * @return Menu item that has {@code manager} listening for 3968 * {@link #action}. 3969 */ 3970 protected JMenuItem buildMenuItem(final UIManager manager) { 3971 JMenuItem item = new JRadioButtonMenuItem(label); 3972 item.setActionCommand(action); 3973 item.addActionListener(manager); 3974 return item; 3975 } 3976 } 3977 3978 /** 3979 * Represents what McIDAS-V {@literal "knows"} about IDV actions. 3980 */ 3981 protected enum ActionAttribute { 3982 3983 /** 3984 * Unique identifier for an IDV action. Required attribute. 3985 * 3986 * @see IdvUIManager#ATTR_ID 3987 */ 3988 ID(ATTR_ID), 3989 3990 /** 3991 * Path to an icon for this action. Currently required. Note that 3992 * McIDAS-V differs from the IDV in that actions must support different 3993 * icon sizes. This is implemented in McIDAS-V by simply having the value 3994 * of this path be a valid {@literal "format string"}, 3995 * such as {@code image="/edu/wisc/ssec/mcidasv/resources/icons/toolbar/background-image%d.png"} 3996 * 3997 * <p>The upshot is that this value <b>will not be a valid path in 3998 * McIDAS-V</b>. Use either {@link IdvAction#getMenuIcon()} or 3999 * {@link IdvAction#getIconForStyle}. 4000 * 4001 * @see IdvUIManager#ATTR_IMAGE 4002 * @see IdvAction#getRawIconPath() 4003 * @see IdvAction#getMenuIcon() 4004 * @see IdvAction#getIconForStyle 4005 */ 4006 ICON(ATTR_IMAGE), 4007 4008 /** 4009 * Brief description of a IDV action. Required attribute. 4010 * @see IdvUIManager#ATTR_DESCRIPTION 4011 */ 4012 DESCRIPTION(ATTR_DESCRIPTION), 4013 4014 /** 4015 * Allows actions to be clustered into arbitrary groups. Currently 4016 * optional; defaults to {@literal "General"}. 4017 * @see IdvUIManager#ATTR_GROUP 4018 */ 4019 GROUP(ATTR_GROUP, "General"), 4020 4021 /** 4022 * Actual method call used to invoke a given IDV action. Required 4023 * attribute. 4024 * @see IdvUIManager#ATTR_ACTION 4025 */ 4026 ACTION(ATTR_ACTION); 4027 4028 /** 4029 * A blank {@link String} if this is a required attribute, or a 4030 * {@code String} value to use in case this attribute has not been 4031 * specified by a given IDV action. 4032 */ 4033 private final String defaultValue; 4034 4035 /** 4036 * String representation of this attribute as used by the IDV. 4037 * @see #asIdvString() 4038 */ 4039 private final String idvString; 4040 4041 /** Whether or not this attribute is required. */ 4042 private final boolean required; 4043 4044 /** 4045 * Creates a constant that represents a required IDV action attribute. 4046 * 4047 * @param idvString Corresponding IDV attribute {@link String}. Cannot be {@code null}. 4048 * 4049 * @throws NullPointerException if {@code idvString} is {@code null}. 4050 */ 4051 ActionAttribute(final String idvString) { 4052 requireNonNull(idvString, "Cannot be associated with a null IDV action attribute String"); 4053 4054 this.idvString = idvString; 4055 this.defaultValue = ""; 4056 this.required = true; 4057 } 4058 4059 /** 4060 * Creates a constant that represents an optional IDV action attribute. 4061 * 4062 * @param idvString Corresponding IDV attribute {@link String}. 4063 * Cannot be {@code null}. 4064 * @param defValue Default value for actions that do not have this 4065 * attribute. Cannot be {@code null} or an empty {@code String}. 4066 * 4067 * @throws NullPointerException if either {@code idvString} or 4068 * {@code defValue} is {@code null}. 4069 * @throws IllegalArgumentException if {@code defValue} is an empty 4070 * {@code String}. 4071 * 4072 */ 4073 ActionAttribute(final String idvString, final String defValue) { 4074 requireNonNull(idvString, "Cannot be associated with a null IDV action attribute String"); 4075 Contract.notNull(defValue, "Optional action attribute \"%s\" requires a non-null default value", toString()); 4076 Contract.checkArg(!defValue.equals(""), "Optional action attribute \"%s\" requires something more descriptive than an empty String", toString()); 4077 4078 this.idvString = idvString; 4079 this.defaultValue = defValue; 4080 this.required = (defaultValue.equals("")); 4081 } 4082 4083 /** 4084 * @return The {@link String} representation of this attribute, as is 4085 * used by the IDV. 4086 * 4087 * @see IdvUIManager#ATTR_ACTION 4088 * @see IdvUIManager#ATTR_DESCRIPTION 4089 * @see IdvUIManager#ATTR_GROUP 4090 * @see IdvUIManager#ATTR_ID 4091 * @see IdvUIManager#ATTR_IMAGE 4092 */ 4093 public String asIdvString() { return idvString; } 4094 4095 /** 4096 * @return {@literal "Default value"} for this attribute. 4097 * Blank {@link String}s imply that the attribute is required (and 4098 * thus lacks a true default value). 4099 */ 4100 public String defaultValue() { return defaultValue; } 4101 4102 /** 4103 * @return Whether or not this attribute is a required attribute for 4104 * valid {@link IdvAction}s. 4105 */ 4106 public boolean isRequired() { return required; } 4107 } 4108 4109 /** 4110 * Represents the set of known {@link IdvAction}s in an idiom that can be 4111 * easily used by both the IDV and McIDAS-V. 4112 */ 4113 // TODO(jon:101): use Sets instead of maps and whatnot 4114 // TODO(jon:103): create an invalid IdvAction 4115 public static final class IdvActions { 4116 4117 /** Maps {@literal "id"} values to {@link IdvAction}s. */ 4118 private final Map<String, IdvAction> idToAction = new ConcurrentHashMap<>(); 4119 4120 /** Collects {@link IdvAction}s {@literal "under"} common group values. */ 4121 // TODO(jon:102): this should probably become concurrency-friendly. 4122 private final Map<String, Set<IdvAction>> groupToActions = new LinkedHashMap<>(); 4123 4124 /** 4125 * 4126 * 4127 * @param idv Reference to the IDV {@literal "god"} object. Cannot be {@code null}. 4128 * @param collectionId IDV resource collection that contains our actions. Cannot be {@code null}. 4129 * 4130 * @throws NullPointerException if {@code idv} or {@code collectionId} 4131 * is {@code null}. 4132 */ 4133 public IdvActions(final IntegratedDataViewer idv, final XmlIdvResource collectionId) { 4134 requireNonNull(idv, "Cannot provide a null IDV reference"); 4135 requireNonNull(collectionId, "Cannot build actions from a null collection id"); 4136 4137 // i lub u xpath (but how much slower is this?) 4138 String query = "//action[@id and @image and @description and @action]"; 4139 for (Element e : elements(idv, collectionId, query)) { 4140 IdvAction a = new IdvAction(e); 4141 String id = a.getAttribute(ActionAttribute.ID); 4142 idToAction.put(id, a); 4143 String group = a.getAttribute(ActionAttribute.GROUP); 4144 if (!groupToActions.containsKey(group)) { 4145 groupToActions.put(group, new LinkedHashSet<IdvAction>()); 4146 } 4147 Set<IdvAction> groupedIds = groupToActions.get(group); 4148 groupedIds.add(a); 4149 } 4150 } 4151 4152 /** 4153 * Attempts to return the {@link IdvAction} associated with the given 4154 * {@code actionId}. 4155 * 4156 * @param actionId Identifier to use in the search. Cannot be 4157 * {@code null}. 4158 * 4159 * @return Either the {@code IdvAction} that matches {@code actionId} 4160 * or {@code null} if there was no match. 4161 * 4162 * @throws NullPointerException if {@code actionId} is {@code null}. 4163 */ 4164 // TODO(jon:103) here 4165 public IdvAction getAction(final String actionId) { 4166 requireNonNull(actionId, "Null action identifiers are not allowed"); 4167 return idToAction.get(actionId); 4168 } 4169 4170 /** 4171 * Searches for the action associated with {@code actionId} and 4172 * returns the value associated with the given {@link ActionAttribute}. 4173 * 4174 * @param actionId Identifier to search for. Cannot be {@code null}. 4175 * @param attr Attribute whose value is desired. Cannot be {@code null}. 4176 * 4177 * @return Either the desired attribute value of the desired action, 4178 * or {@code null} if {@code actionId} has no associated action. 4179 * 4180 * @throws NullPointerException if either {@code actionId} or 4181 * {@code attr} is {@code null}. 4182 */ 4183 // TODO(jon:103) here 4184 public String getAttributeForAction(final String actionId, final ActionAttribute attr) { 4185 requireNonNull(actionId, "Null action identifiers are not allowed"); 4186 requireNonNull(attr, "Actions cannot have values associated with a null attribute"); 4187 IdvAction action = idToAction.get(actionId); 4188 if (action == null) { 4189 return null; 4190 } 4191 return action.getAttribute(attr); 4192 } 4193 4194 /** 4195 * Attempts to return the XML {@link Element} that {@literal "represents"} the 4196 * action associated with {@code actionId}. 4197 * 4198 * @param actionId Identifier whose XML element is desired. Cannot be {@code null}. 4199 * 4200 * @return Either the XML element associated with {@code actionId} or {@code null}. 4201 * 4202 * @throws NullPointerException if {@code actionId} is {@code null}. 4203 * 4204 * @see IdvAction#originalElement 4205 */ 4206 // TODO(jon:103) here 4207 public Element getElementForAction(final String actionId) { 4208 requireNonNull(actionId, "Cannot search for a null action identifier"); 4209 IdvAction action = idToAction.get(actionId); 4210 if (action == null) { 4211 return null; 4212 } 4213 return action.getElement(); 4214 } 4215 4216 /** 4217 * Attempts to return an {@link Icon} for a given {@link ActionAttribute#ID} and 4218 * {@link ToolbarStyle}. 4219 * 4220 * @param actionId ID of the action whose {@literal "styled"} icon is 4221 * desired. Cannot be {@code null}. 4222 * @param style Desired {@code Icon} style. Cannot be {@code null}. 4223 * 4224 * @return Either the {@code Icon} associated with {@code actionId} 4225 * and {@code style}, or {@code null}. 4226 * 4227 * @throws NullPointerException if either {@code actionId} or 4228 * {@code style} is {@code null}. 4229 */ 4230 // TODO(jon:103) here 4231 public Icon getStyledIconFor(final String actionId, final ToolbarStyle style) { 4232 requireNonNull(actionId, "Cannot get an icon for a null action identifier"); 4233 requireNonNull(style, "Cannot get an icon for a null ToolbarStyle"); 4234 IdvAction a = idToAction.get(actionId); 4235 if (a == null) { 4236 return null; 4237 } 4238 return a.getIconForStyle(style); 4239 } 4240 4241 // TODO(jon:105): replace with something better 4242 public List<String> getAttributes(final ActionAttribute attr) { 4243 requireNonNull(attr, "Actions cannot have null attributes"); 4244 List<String> attributeList = arrList(idToAction.size()); 4245 for (Map.Entry<String, IdvAction> entry : idToAction.entrySet()) { 4246 attributeList.add(entry.getValue().getAttribute(attr)); 4247 } 4248 return attributeList; 4249 } 4250 4251 /** 4252 * @return List of all known {@code IdvAction}s. 4253 */ 4254 public List<IdvAction> getAllActions() { 4255 return arrList(idToAction.values()); 4256 } 4257 4258 /** 4259 * @return List of all known action groupings. 4260 * 4261 * @see ActionAttribute#GROUP 4262 * @see #getActionsForGroup(String) 4263 */ 4264 public List<String> getAllGroups() { 4265 return arrList(groupToActions.keySet()); 4266 } 4267 4268 /** 4269 * Returns the {@link Set} of {@link IdvAction}s associated with the 4270 * given {@code group}. 4271 * 4272 * @param group Group whose associated actions you want. Cannot be 4273 * {@code null}. 4274 * 4275 * @return Collection of {@code IdvAction}s associated with 4276 * {@code group}. A blank collection is returned if there are no actions 4277 * associated with {@code group}. 4278 * 4279 * @throws NullPointerException if {@code group} is {@code null}. 4280 * 4281 * @see ActionAttribute#GROUP 4282 * @see #getAllGroups() 4283 */ 4284 public Set<IdvAction> getActionsForGroup(final String group) { 4285 requireNonNull(group, "Actions cannot be associated with a null group"); 4286 if (!groupToActions.containsKey(group)) { 4287 return Collections.emptySet(); 4288 } 4289 return groupToActions.get(group); 4290 } 4291 4292 /** 4293 * Returns a summary of the known IDV actions. Please note that this 4294 * format is subject to change, and is not intended for serialization. 4295 * 4296 * @return String that looks like 4297 * {@code [IdvActions@HASHCODE: actions=...]}. 4298 */ 4299 @Override public String toString() { 4300 return String.format("[IdvActions@%x: actions=%s]", hashCode(), idToAction); 4301 } 4302 } 4303 4304 /** 4305 * Represents an individual IDV action. Should be fairly adaptable to 4306 * unforeseen changes from Unidata? 4307 */ 4308 // TODO(jon:106): Implement equals/hashCode so that you can use these in Sets. The only relevant value should be the id, right? 4309 public static final class IdvAction { 4310 4311 /** The XML {@link Element} that represents this IDV action. */ 4312 private final Element originalElement; 4313 4314 /** Mapping of (known) XML attributes to values for this individual action. */ 4315 private final Map<ActionAttribute, String> attributes; 4316 4317 /** 4318 * Simple {@literal "cache"} for the different icons this action has 4319 * displayed. This is {@literal "lazy"}, so the cache does not contain 4320 * icons for {@link ToolbarStyle}s that haven't been used. 4321 */ 4322 private final Map<ToolbarStyle, Icon> iconCache = new ConcurrentHashMap<>(); 4323 4324 /** 4325 * Creates a representation of an IDV action using a given {@link Element}. 4326 * 4327 * @param element XML representation of an IDV action. Cannot be {@code null}. 4328 * 4329 * @throws NullPointerException if {@code element} is {@code null}. 4330 * @throws IllegalArgumentException if {@code element} is not a valid IDV action. 4331 * 4332 * @see UIManager#isValidIdvAction(Element) 4333 */ 4334 public IdvAction(final Element element) { 4335 requireNonNull(element, "Cannot build an action from a null element"); 4336 // TODO(jon:107): need a way to diagnose what's wrong with the action? 4337 Contract.checkArg(isValidIdvAction(element), "Action lacks required attributes"); 4338 originalElement = element; 4339 attributes = actionElementToMap(element); 4340 } 4341 4342 /** 4343 * @return Returns the {@literal "raw"} path to the icon associated 4344 * with this action. Remember that this is actually a {@literal "format string"} 4345 * and should not be considered a valid path! 4346 * 4347 * @see #getIconForStyle 4348 */ 4349 public String getRawIconPath() { 4350 return attributes.get(ActionAttribute.ICON); 4351 } 4352 4353 /** 4354 * @return Returns the {@link Icon} associated with {@link ToolbarStyle#SMALL}. 4355 */ 4356 public Icon getMenuIcon() { 4357 return getIconForStyle(ToolbarStyle.SMALL); 4358 } 4359 4360 /** 4361 * Returns the {@link Icon} associated with this action and the given 4362 * {@link ToolbarStyle}. 4363 * 4364 * @param style {@literal "Style"} of the {@code Icon} to be returned. 4365 * Cannot be {@code null}. 4366 * 4367 * @return This action's {@code Icon} with {@code style} {@literal "applied."} 4368 * 4369 * @see ActionAttribute#ICON 4370 * @see #iconCache 4371 */ 4372 public Icon getIconForStyle(final ToolbarStyle style) { 4373 requireNonNull(style, "Cannot build an icon for a null ToolbarStyle"); 4374 if (!iconCache.containsKey(style)) { 4375 String styledPath = String.format(getRawIconPath(), style.getSize()); 4376 URL tmp = getClass().getResource(styledPath); 4377 iconCache.put(style, new ImageIcon(Toolkit.getDefaultToolkit().getImage(tmp))); 4378 } 4379 return iconCache.get(style); 4380 } 4381 4382 /** 4383 * @return Returns the identifier of this {@code IdvAction}. 4384 */ 4385 public String getId() { 4386 return getAttribute(ActionAttribute.ID); 4387 } 4388 4389 /** 4390 * Representation of this {@code IdvAction} as an {@literal "IDV action call"}. 4391 * 4392 * @return String that is suitable to hand off to the IDV for execution. 4393 */ 4394 public String getCommand() { 4395 return "idv.handleAction('action:"+getAttribute(ActionAttribute.ID)+"')"; 4396 } 4397 4398 /** 4399 * Returns the value associated with a given {@link ActionAttribute} 4400 * for this action. 4401 * 4402 * @param attr ActionAttribute whose value you want. Cannot be {@code null}. 4403 * 4404 * @return Value associated with {@code attr}. 4405 * 4406 * @throws NullPointerException if {@code attr} is {@code null}. 4407 */ 4408 public String getAttribute(final ActionAttribute attr) { 4409 requireNonNull(attr, "No values can be associated with a null ActionAttribute"); 4410 return attributes.get(attr); 4411 } 4412 4413 /** 4414 * @return The XML {@link Element} used to create this {@code IdvAction}. 4415 */ 4416 // TODO(jon:104): any way to copy this element? if so, this can become an immutable class! 4417 public Element getElement() { 4418 return originalElement; 4419 } 4420 4421 /** 4422 * Returns a brief description of this action. Please note that the 4423 * format is subject to change and is not intended for serialization. 4424 * 4425 * @return String that looks like {@code [IdvAction@HASHCODE: attributes=...]}. 4426 */ 4427 @Override public String toString() { 4428 return String.format("[IdvAction@%x: attributes=%s]", hashCode(), attributes); 4429 } 4430 } 4431}