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