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