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