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