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