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