001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2015 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 029package edu.wisc.ssec.mcidasv; 030 031import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.arrList; 032import static ucar.unidata.xml.XmlUtil.getAttribute; 033 034import org.slf4j.bridge.SLF4JBridgeHandler; 035 036import java.awt.Insets; 037import java.awt.event.ActionEvent; 038import java.awt.event.ActionListener; 039import java.awt.geom.Rectangle2D; 040import java.io.BufferedReader; 041import java.io.File; 042import java.io.FileOutputStream; 043import java.io.FileReader; 044import java.io.IOException; 045import java.io.PrintStream; 046import java.lang.reflect.Method; 047import java.rmi.RemoteException; 048import java.util.*; 049 050import javax.swing.Icon; 051import javax.swing.JButton; 052import javax.swing.JCheckBox; 053import javax.swing.JComponent; 054import javax.swing.JDialog; 055import javax.swing.JLabel; 056import javax.swing.JOptionPane; 057import javax.swing.SwingUtilities; 058 059import edu.wisc.ssec.mcidasv.util.SystemState; 060import org.bushe.swing.event.annotation.AnnotationProcessor; 061import org.bushe.swing.event.annotation.EventSubscriber; 062import org.slf4j.Logger; 063import org.slf4j.LoggerFactory; 064import org.w3c.dom.Element; 065 066import visad.VisADException; 067 068import ucar.unidata.data.DataManager; 069import ucar.unidata.idv.ArgsManager; 070import ucar.unidata.idv.ControlDescriptor; 071import ucar.unidata.idv.IdvObjectStore; 072import ucar.unidata.idv.IdvPersistenceManager; 073import ucar.unidata.idv.IdvPreferenceManager; 074import ucar.unidata.idv.IdvResourceManager; 075import ucar.unidata.idv.IntegratedDataViewer; 076import ucar.unidata.idv.PluginManager; 077import ucar.unidata.idv.VMManager; 078import ucar.unidata.idv.ViewDescriptor; 079import ucar.unidata.idv.ViewManager; 080import ucar.unidata.idv.chooser.IdvChooserManager; 081import ucar.unidata.idv.ui.IdvUIManager; 082import ucar.unidata.ui.colortable.ColorTableManager; 083import ucar.unidata.ui.InteractiveShell.ShellHistoryEntry; 084import ucar.unidata.util.FileManager; 085import ucar.unidata.util.GuiUtils; 086import ucar.unidata.util.IOUtil; 087import ucar.unidata.util.LogUtil; 088import ucar.unidata.util.Misc; 089import ucar.unidata.xml.XmlDelegateImpl; 090import ucar.unidata.xml.XmlEncoder; 091import ucar.unidata.xml.XmlUtil; 092import uk.org.lidalia.sysoutslf4j.context.LogLevel; 093import uk.org.lidalia.sysoutslf4j.context.SysOutOverSLF4J; 094 095import edu.wisc.ssec.mcidasv.chooser.McIdasChooserManager; 096import edu.wisc.ssec.mcidasv.control.LambertAEA; 097import edu.wisc.ssec.mcidasv.data.McvDataManager; 098import edu.wisc.ssec.mcidasv.monitors.MonitorManager; 099import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntrySource; 100import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryStatus; 101import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryType; 102import edu.wisc.ssec.mcidasv.servermanager.AddeEntry.EntryValidity; 103import edu.wisc.ssec.mcidasv.servermanager.EntryStore; 104import edu.wisc.ssec.mcidasv.servermanager.EntryTransforms; 105import edu.wisc.ssec.mcidasv.servermanager.LocalAddeEntry; 106import edu.wisc.ssec.mcidasv.servermanager.LocalAddeEntry.AddeFormat; 107import edu.wisc.ssec.mcidasv.servermanager.RemoteAddeEntry; 108import edu.wisc.ssec.mcidasv.servermanager.TabbedAddeManager; 109import edu.wisc.ssec.mcidasv.startupmanager.StartupManager; 110import edu.wisc.ssec.mcidasv.ui.LayerAnimationWindow; 111import edu.wisc.ssec.mcidasv.ui.McIdasColorTableManager; 112import edu.wisc.ssec.mcidasv.ui.UIManager; 113import edu.wisc.ssec.mcidasv.util.Contract; 114import edu.wisc.ssec.mcidasv.util.McVGuiUtils; 115import edu.wisc.ssec.mcidasv.util.WebBrowser; 116 117@SuppressWarnings("unchecked") 118public class McIDASV extends IntegratedDataViewer { 119 120 private static final Logger logger = LoggerFactory.getLogger(McIDASV.class); 121 122 /** Set at the beginning of {@link #main(String[])}. */ 123 private static long startTime; 124 125 /** Set at the end of {@link #initDone()}. */ 126 private static long estimate; 127 128 /** 129 * Path to a {@literal "session"} file--it's created upon McIDAS-V 130 * starting and removed when McIDAS-V exits cleanly. This allows us to 131 * perform a primitive check to see if the current session has happened 132 * after a crash. 133 */ 134 private static String SESSION_FILE = getSessionFilePath(); 135 136 private static boolean cleanExit = true; 137 138 private static Date previousStart = null; 139 140 /** Set to true only if "-forceaqua" was found in the command line. */ 141 public static boolean useAquaLookAndFeel = false; 142 143 /** Points to the adde image defaults. */ 144 public static final IdvResourceManager.XmlIdvResource RSC_FRAMEDEFAULTS = 145 new IdvResourceManager.XmlIdvResource("idv.resource.framedefaults", 146 "McIDAS-X Frame Defaults"); 147 148 /** Points to the server definitions. */ 149 public static final IdvResourceManager.XmlIdvResource RSC_SERVERS = 150 new IdvResourceManager.XmlIdvResource("idv.resource.servers", 151 "Servers", "servers\\.xml$"); 152 153 /** Used to access McIDAS-V state in a static context. */ 154 private static McIDASV staticMcv; 155 156 /** Accessory in file save dialog */ 157 private JCheckBox overwriteDataCbx = 158 new JCheckBox("Change data paths", false); 159 160 /** The chooser manager */ 161 protected McIdasChooserManager chooserManager; 162 163 /** The http based monitor to dump stack traces and shutdown the IDV */ 164 private McIDASVMonitor mcvMonitor; 165 166 /** {@link MonitorManager} allows for relatively easy and efficient monitoring of various resources. */ 167 private final MonitorManager monitorManager = new MonitorManager(); 168 169 /** Actions passed into {@link #handleAction(String, Hashtable, boolean)}. */ 170 private final List<String> actions = new LinkedList<>(); 171 172 private enum WarningResult { OK, CANCEL, SHOW, HIDE }; 173 174 private EntryStore addeEntries; 175 176 private TabbedAddeManager tabbedAddeManager = null; 177 178 /** 179 * Create the McIDASV with the given command line arguments. 180 * This constructor calls {@link IntegratedDataViewer#init()} 181 * 182 * @param args Command line arguments 183 * @exception VisADException from construction of VisAd objects 184 * @exception RemoteException from construction of VisAD objects 185 */ 186 public McIDASV(String[] args) throws VisADException, RemoteException { 187 super(args); 188 189 AnnotationProcessor.process(this); 190 191 staticMcv = this; 192 193 // Keep this code around for reference--this requires MacMenuManager.java and MRJToolkit. 194 // We use OSXAdapter instead now, but it is unclear which is the preferred method. 195 // Let's use the one that works. 196// if (isMac()) { 197// try { 198// Object[] constructor_args = { this }; 199// Class[] arglist = { McIDASV.class }; 200// Class mac_class = Class.forName("edu.wisc.ssec.mcidasv.MacMenuManager"); 201// Constructor new_one = mac_class.getConstructor(arglist); 202// new_one.newInstance(constructor_args); 203// } 204// catch(Exception e) { 205// System.out.println(e); 206// } 207// } 208 209 // Set up our application to respond to the Mac OS X application menu 210 registerForMacOSXEvents(); 211 212 // This doesn't always look good... but keep it here for future reference 213// UIDefaults def = javax.swing.UIManager.getLookAndFeelDefaults(); 214// Enumeration defkeys = def.keys(); 215// while (defkeys.hasMoreElements()) { 216// Object item = defkeys.nextElement(); 217// if (item.toString().indexOf("selectionBackground") > 0) { 218// def.put(item, Constants.MCV_BLUE); 219// } 220// } 221 222 // we're tired of the IDV's default missing image, so reset it 223 GuiUtils.MISSING_IMAGE = "/edu/wisc/ssec/mcidasv/resources/icons/toolbar/mcidasv-round22.png"; 224 225 this.init(); 226 } 227 228 // Generic registration with the Mac OS X application menu 229 // Checks the platform, then attempts to register with the Apple EAWT 230 // See OSXAdapter.java to see how this is done without directly referencing any Apple APIs 231 public void registerForMacOSXEvents() { 232 if (isMac()) { 233 try { 234 // Generate and register the OSXAdapter, passing it a hash of all the methods we wish to 235 // use as delegates for various com.apple.eawt.ApplicationListener methods 236 Class<?> thisClass = getClass(); 237 Class<?>[] args = (Class[])null; 238 OSXAdapter.setQuitHandler(this, thisClass.getDeclaredMethod("MacOSXQuit", args)); 239 OSXAdapter.setAboutHandler(this, thisClass.getDeclaredMethod("MacOSXAbout", args)); 240 OSXAdapter.setPreferencesHandler(this, thisClass.getDeclaredMethod("MacOSXPreferences", args)); 241 } catch (Exception e) { 242 logger.error("Error while loading the OSXAdapter", e); 243 } 244 } 245 } 246 247 public boolean MacOSXQuit() { 248 return quit(); 249 } 250 251 public void MacOSXAbout() { 252 getIdvUIManager().about(); 253 } 254 255 public void MacOSXPreferences() { 256 showPreferenceManager(); 257 } 258 259 /** 260 * Get the maximum number of threads to be used when rendering in VisAD. 261 * 262 * @return Number of threads for rendering. Default value is the same as 263 * {@link Runtime#availableProcessors()}. 264 */ 265 @Override public int getMaxRenderThreadCount() { 266 StateManager stateManager = (StateManager)getStateManager(); 267 return stateManager.getPropertyOrPreference(PREF_THREADS_RENDER, 268 Runtime.getRuntime().availableProcessors()); 269 } 270 271 /** 272 * Get the maximum number of threads to be used when reading data. 273 * 274 * @return Number of threads for reading data. Default value is {@code 4}. 275 */ 276 @Override public int getMaxDataThreadCount() { 277 StateManager stateManager = (StateManager)getStateManager(); 278 return stateManager.getPropertyOrPreference(PREF_THREADS_DATA, 4); 279 } 280 281 /** 282 * Start up the McIDAS-V monitor server. This is an http server on the port defined 283 * by the property idv.monitorport (8788). It is only accessible to 127.0.0.1 (localhost) 284 */ 285 // TODO: we probably don't want our own copy of this in the long run... 286 // all we did was change "IDV" to "McIDAS-V" 287 @Override protected void startMonitor() { 288 if (mcvMonitor != null) { 289 return; 290 } 291 final String monitorPort = getProperty(PROP_MONITORPORT, ""); 292 if (monitorPort!=null && monitorPort.trim().length()>0 && !"none".equals(monitorPort.trim())) { 293 Misc.run(new Runnable() { 294 @Override public void run() { 295 try { 296 mcvMonitor = new McIDASVMonitor(McIDASV.this, Integer.parseInt(monitorPort)); 297 mcvMonitor.init(); 298 } catch (Exception exc) { 299 LogUtil.consoleMessage("Unable to start McIDAS-V monitor on port:" + monitorPort); 300 LogUtil.consoleMessage("Error:" + exc); 301 } 302 } 303 }); 304 } 305 } 306 307 /** 308 * Initializes a XML encoder with McIDAS-V specific XML delegates. 309 * 310 * @param encoder XML encoder that'll be dealing with persistence. 311 * @param forRead Not used as of yet. 312 */ 313 // TODO: if we ever get up past three or so XML delegates, I vote that we 314 // make our own version of VisADPersistence. 315 @Override protected void initEncoder(XmlEncoder encoder, boolean forRead) { 316 317 encoder.addDelegateForClass(LambertAEA.class, new XmlDelegateImpl() { 318 @Override public Element createElement(XmlEncoder e, Object o) { 319 LambertAEA projection = (LambertAEA)o; 320 Rectangle2D rect = projection.getDefaultMapArea(); 321 List args = Misc.newList(rect); 322 List types = Misc.newList(rect.getClass()); 323 return e.createObjectConstructorElement(o, args, types); 324 } 325 }); 326 327 encoder.addDelegateForClass(ShellHistoryEntry.class, new XmlDelegateImpl() { 328 @Override public Element createElement(XmlEncoder e, Object o) { 329 ShellHistoryEntry entry = (ShellHistoryEntry)o; 330 List args = Misc.newList(entry.getEntryBytes(), entry.getInputMode().toString()); 331 return e.createObjectConstructorElement(o, args); 332 } 333 }); 334 335 // TODO(jon): ultra fashion makeover!! 336 encoder.addDelegateForClass(RemoteAddeEntry.class, new XmlDelegateImpl() { 337 @Override public Element createElement(XmlEncoder e, Object o) { 338 RemoteAddeEntry entry = (RemoteAddeEntry)o; 339 Element element = e.createObjectElement(o.getClass()); 340 element.setAttribute("address", entry.getAddress()); 341 element.setAttribute("group", entry.getGroup()); 342 element.setAttribute("username", entry.getAccount().getUsername()); 343 element.setAttribute("project", entry.getAccount().getProject()); 344 element.setAttribute("source", entry.getEntrySource().toString()); 345 element.setAttribute("type", entry.getEntryType().toString()); 346 element.setAttribute("validity", entry.getEntryValidity().toString()); 347 element.setAttribute("status", entry.getEntryStatus().toString()); 348 element.setAttribute("temporary", Boolean.toString(entry.isEntryTemporary())); 349 element.setAttribute("alias", entry.getEntryAlias()); 350 return element; 351 } 352 353 @Override public Object createObject(XmlEncoder e, Element element) { 354 String address = getAttribute(element, "address"); 355 String group = getAttribute(element, "group"); 356 String username = getAttribute(element, "username"); 357 String project = getAttribute(element, "project"); 358 String source = getAttribute(element, "source"); 359 String type = getAttribute(element, "type"); 360 String validity = getAttribute(element, "validity"); 361 String status = getAttribute(element, "status"); 362 boolean temporary = getAttribute(element, "temporary", false); 363 String alias = getAttribute(element, "alias", ""); 364 365 EntrySource entrySource = EntryTransforms.strToEntrySource(source); 366 EntryType entryType = EntryTransforms.strToEntryType(type); 367 EntryValidity entryValidity = EntryTransforms.strToEntryValidity(validity); 368 EntryStatus entryStatus = EntryTransforms.strToEntryStatus(status); 369 370 RemoteAddeEntry entry = 371 new RemoteAddeEntry.Builder(address, group) 372 .account(username, project) 373 .source(entrySource) 374 .type(entryType) 375 .validity(entryValidity) 376 .temporary(temporary) 377 .alias(alias) 378 .status(entryStatus).build(); 379 380 return entry; 381 } 382 }); 383 384 encoder.addDelegateForClass(LocalAddeEntry.class, new XmlDelegateImpl() { 385 @Override public Element createElement(XmlEncoder e, Object o) { 386 LocalAddeEntry entry = (LocalAddeEntry)o; 387 Element element = e.createObjectElement(o.getClass()); 388 element.setAttribute("group", entry.getGroup()); 389 element.setAttribute("descriptor", entry.getDescriptor()); 390 element.setAttribute("realtime", entry.getRealtimeAsString()); 391 element.setAttribute("format", entry.getFormat().name()); 392 element.setAttribute("start", entry.getStart()); 393 element.setAttribute("end", entry.getEnd()); 394 element.setAttribute("fileMask", entry.getMask()); 395 element.setAttribute("name", entry.getName()); 396 element.setAttribute("status", entry.getEntryStatus().name()); 397 element.setAttribute("temporary", Boolean.toString(entry.isEntryTemporary())); 398 element.setAttribute("alias", entry.getEntryAlias()); 399 return element; 400 } 401 @Override public Object createObject(XmlEncoder e, Element element) { 402 String group = getAttribute(element, "group"); 403 String descriptor = getAttribute(element, "descriptor"); 404 String realtime = getAttribute(element, "realtime", ""); 405 AddeFormat format = EntryTransforms.strToAddeFormat(XmlUtil.getAttribute(element, "format")); 406 String start = getAttribute(element, "start", "1"); 407 String end = getAttribute(element, "end", "999999"); 408 String fileMask = getAttribute(element, "fileMask"); 409 String name = getAttribute(element, "name"); 410 String status = getAttribute(element, "status", "ENABLED"); 411 boolean temporary = getAttribute(element, "temporary", false); 412 String alias = getAttribute(element, "alias", ""); 413 414 LocalAddeEntry entry = 415 new LocalAddeEntry.Builder(name, group, fileMask, format) 416 .range(start, end) 417 .descriptor(descriptor) 418 .realtime(realtime) 419 .status(status) 420 .temporary(temporary) 421 .alias(alias).build(); 422 423 return entry; 424 } 425 }); 426 427// encoder.addHighPriorityDelegateForClass(AddeImageInfo.class, new XmlDelegateImpl() { 428// @Override public Element createElement(XmlEncoder e, Object o) { 429// AddeImageInfo info = (AddeImageInfo)o; 430// String user = info.getUser(); 431// int proj = info.getProject(); 432// logger.trace("user={} proj={}", new Object[] { user, proj }); 433// return e.createElementDontCheckDelegate(o); 434// } 435// @Override public Object createObject(XmlEncoder e, Element element) { 436// String host = getAttribute(element, "Host"); 437// String group = getAttribute(element, "Group"); 438// String descriptor = getAttribute(element, "Descriptor"); 439// String type = getAttribute(element, "RequestType"); 440// 441// EntryStore store = getServerManager(); 442// boolean mcservRunning = store.checkLocalServer(); 443// boolean isKnown = store.searchWithPrefix(host+'!'+group).isEmpty(); 444// 445// logger.trace("isKnown={} host='{}' group='{}' type='{}' desc='{}'", new Object[] { isKnown, host, group, descriptor, type }); 446// return e.createObjectDontCheckDelegate(element); 447// } 448// }); 449 450// encoder.addHighPriorityDelegateForClass(AddeImageDescriptor.class, new XmlDelegateImpl() { 451// @Override public Element createElement(XmlEncoder e, Object o) { 452// AddeImageDescriptor desc = (AddeImageDescriptor)o; 453// String source = desc.getSource(); 454// desc.setSource(source.replace("USER", "user")); 455// return desc.createElement(e, o); 456// } 457// @Override public Object createObject(XmlEncoder e, Element element) { 458// 459// return e.createObjectDontCheckDelegate(element); 460// } 461// }); 462 463 /** 464 * Move legacy classes to a new location 465 */ 466 encoder.registerNewClassName("edu.wisc.ssec.mcidasv.data.Test2ImageDataSource", 467 "edu.wisc.ssec.mcidasv.data.adde.AddeImageParameterDataSource"); 468 encoder.registerNewClassName("edu.wisc.ssec.mcidasv.data.Test2AddeImageDataSource", 469 "edu.wisc.ssec.mcidasv.data.adde.AddeImageParameterDataSource"); 470 encoder.registerNewClassName("edu.wisc.ssec.mcidasv.data.AddePointDataSource", 471 "edu.wisc.ssec.mcidasv.data.adde.AddePointDataSource"); 472 encoder.registerNewClassName("edu.wisc.ssec.mcidasv.data.AddeSoundingAdapter", 473 "edu.wisc.ssec.mcidasv.data.adde.AddeSoundingAdapter"); 474 } 475 476 /** 477 * Returns <i>all</i> of the actions used in this McIDAS-V session. This is 478 * possibly TMI and might be removed... 479 * 480 * @return Actions executed thus far. 481 */ 482 public List<String> getActionHistory() { 483 return actions; 484 } 485 486 /** 487 * Converts {@link ArgsManager#getOriginalArgs()} to a {@link List} and 488 * returns. 489 * 490 * @return The command-line arguments used to start McIDAS-V, as an 491 * {@code ArrayList}. 492 */ 493 public List<String> getCommandLineArgs() { 494 String[] originalArgs = getArgsManager().getOriginalArgs(); 495 List<String> args = arrList(originalArgs.length); 496 Collections.addAll(args, originalArgs); 497 return args; 498 } 499 500 /** 501 * Captures the action passed to {@code handleAction}. The action is logged 502 * and additionally, if the action is a HTML link, we attempt to visit the 503 * link in the user's preferred browser. 504 */ 505 @Override public boolean handleAction(String action, Hashtable properties, 506 boolean checkForAlias) 507 { 508 actions.add(action); 509 510 if (IOUtil.isHtmlFile(action)) { 511 WebBrowser.browse(action); 512 return true; 513 } 514 515 return super.handleAction(action, properties, checkForAlias); 516 } 517 518 /** 519 * This method checks if the given action is one of the following. 520 * <ul> 521 * <li>Jython code: starts with {@literal "jython:"}.</li> 522 * <li>Help link: starts with {@literal "help:"}.</li> 523 * <li>Resource bundle file: ends with {@literal ".rbi"}.</li> 524 * <li>Bundle file: ends with {@literal ".xidv"}.</li> 525 * <li>JNLP file: ends with {@literal ".jnlp"}.</li> 526 * </ul> 527 * 528 * <p>It returns {@code true} if the action is one of these. {@code false} 529 * otherwise.</p> 530 * 531 * @param action The string action 532 * @param properties any properties 533 * 534 * @return {@code true} if the action was {@literal "handled"}; 535 * {@code false} otherwise. 536 */ 537 @Override protected boolean handleFileOrUrlAction(String action, Hashtable properties) { 538 boolean result = false; 539 boolean idvAction = action.startsWith("idv:"); 540 boolean jythonAction = action.startsWith("jython:"); 541 542 if (!idvAction && !jythonAction) { 543 return super.handleFileOrUrlAction(action, properties); 544 } 545 546 Map<String, Object> hashProps; 547 if (properties != null) { 548 hashProps = new HashMap<>(properties); 549 } else { 550 //noinspection CollectionWithoutInitialCapacity 551 hashProps = new HashMap<>(); 552 } 553 554 ucar.unidata.idv.JythonManager jyManager = getJythonManager(); 555 if (idvAction) { 556 action = action.replace("&", "&").substring(4); 557// getJythonManager().evaluateUntrusted(action, hashProps); 558 jyManager.evaluateUntrusted(action, hashProps); 559 result = true; 560 } else if (jythonAction) { 561 action = action.substring(7); 562 jyManager.evaluateTrusted(action, hashProps); 563// ucar.unidata.idv.JythonManager jyMan = new ucar.unidata.idv.JythonManager(); 564// jyMan 565// getJythonManager().evaluateTrusted(action, hashProps); 566 result = true; 567 } else { 568 result = super.handleFileOrUrlAction(action, properties); 569 } 570 return result; 571 } 572 573 /** 574 * Add a new {@link ControlDescriptor} into the {@code controlDescriptor} 575 * list and {@code controlDescriptorMap}. 576 * 577 * <p>This method differs from the IDV's in that McIDAS-V <b>overwrites</b> 578 * existing {@code ControlDescriptor}s if 579 * {@link ControlDescriptor#getControlId()} matches. 580 * 581 * @param cd The ControlDescriptor to add. 582 * 583 * @throws NullPointerException if {@code cd} is {@code null}. 584 */ 585 @Override protected void addControlDescriptor(ControlDescriptor cd) { 586 cd = Objects.requireNonNull(cd, "Cannot add a null control descriptor to the list of control descriptors."); 587 String id = cd.getControlId(); 588 if (controlDescriptorMap.get(id) == null) { 589 controlDescriptors.add(cd); 590 controlDescriptorMap.put(id, cd); 591 } else { 592 for (int i = 0; i < controlDescriptors.size(); i++) { 593 ControlDescriptor tmp = (ControlDescriptor)controlDescriptors.get(i); 594 if (tmp.getControlId().equals(id)) { 595 controlDescriptors.set(i, cd); 596 controlDescriptorMap.put(id, cd); 597 break; 598 } 599 } 600 } 601 } 602 603 // pop up an incredibly rudimentary window that controls layer viz animation. 604 public void showLayerVisibilityAnimator() { 605 logger.trace("probably should try to do something here."); 606 SwingUtilities.invokeLater(new Runnable() { 607 public void run() { 608 try { 609 LayerAnimationWindow window = new LayerAnimationWindow(); 610 window.setVisible(true); 611 } catch (Exception e) { 612 logger.error("oh no! something happened!", e); 613 } 614 } 615 }); 616 } 617 618 /** 619 * Handles removing all loaded data sources. 620 * 621 * <p>If {@link ArgsManager#getIsOffScreen()} is {@code true}, this method 622 * will ignore the user's preferences and remove all data sources. 623 * 624 * @param showWarning Whether or not to display a warning message before 625 * removing <i>all</i> data sources. See the return details for more. 626 * 627 * @return Either {@code true} if the user wants to continue showing the 628 * warning dialog, or {@code false} if they've elected to stop showing the 629 * warning. If {@code showWarning} is {@code false}, this method will 630 * always return {@code false}, as the user isn't interested in seeing the 631 * warning. 632 */ 633 public boolean removeAllData(final boolean showWarning) { 634 boolean reallyRemove = false; 635 boolean continueWarning = true; 636 637 if (getArgsManager().getIsOffScreen()) { 638 super.removeAllDataSources(); 639 return continueWarning; 640 } 641 642 if (showWarning) { 643 Set<WarningResult> result = showWarningDialog( 644 "Confirm Data Removal", 645 "This action will remove all of the data currently loaded in McIDAS-V.<br>Is this what you want to do?", 646 Constants.PREF_CONFIRM_REMOVE_DATA, 647 "Always ask?", 648 "Remove all data", 649 "Do not remove any data"); 650 reallyRemove = result.contains(WarningResult.OK); 651 continueWarning = result.contains(WarningResult.SHOW); 652 } else { 653 // user doesn't want to see warning messages. 654 reallyRemove = true; 655 continueWarning = false; 656 } 657 658 if (reallyRemove) { 659 super.removeAllDataSources(); 660 } 661 662 return continueWarning; 663 } 664 665 /** 666 * Handles removing all loaded layers ({@literal "displays"} in IDV-land). 667 * 668 * <p>If {@link ArgsManager#getIsOffScreen()} is {@code true}, this method 669 * will ignore the user's preferences and remove all layers. 670 * 671 * @param showWarning Whether or not to display a warning message before 672 * removing <i>all</i> layers. See the return details for more. 673 * 674 * @return Either {@code true} if the user wants to continue showing the 675 * warning dialog, or {@code false} if they've elected to stop showing the 676 * warning. If {@code showWarning} is {@code false}, this method will 677 * always return {@code false}, as the user isn't interested in seeing the 678 * warning. 679 */ 680 public boolean removeAllLayers(final boolean showWarning) { 681 boolean reallyRemove = false; 682 boolean continueWarning = true; 683 684 if (getArgsManager().getIsOffScreen()) { 685 super.removeAllDisplays(); 686 ((ViewManagerManager)getVMManager()).disableAllLayerVizAnimations(); 687 return continueWarning; 688 } 689 690 if (showWarning) { 691 Set<WarningResult> result = showWarningDialog( 692 "Confirm Layer Removal", 693 "This action will remove every layer currently loaded in McIDAS-V.<br>Is this what you want to do?", 694 Constants.PREF_CONFIRM_REMOVE_LAYERS, 695 "Always ask?", 696 "Remove all layers", 697 "Do not remove any layers"); 698 reallyRemove = result.contains(WarningResult.OK); 699 continueWarning = result.contains(WarningResult.SHOW); 700 } else { 701 // user doesn't want to see warning messages. 702 reallyRemove = true; 703 continueWarning = false; 704 } 705 706 if (reallyRemove) { 707 super.removeAllDisplays(); 708 ((ViewManagerManager)getVMManager()).disableAllLayerVizAnimations(); 709 } 710 711 return continueWarning; 712 } 713 714 /** 715 * Overridden so that McIDAS-V can prompt the user before removing, if 716 * necessary. 717 */ 718 @Override public void removeAllDataSources() { 719 IdvObjectStore store = getStore(); 720 boolean showWarning = store.get(Constants.PREF_CONFIRM_REMOVE_DATA, true); 721 showWarning = removeAllData(showWarning); 722 store.put(Constants.PREF_CONFIRM_REMOVE_DATA, showWarning); 723 } 724 725 /** 726 * Overridden so that McIDAS-V can prompt the user before removing, if 727 * necessary. 728 */ 729 @Override public void removeAllDisplays() { 730 IdvObjectStore store = getStore(); 731 boolean showWarning = store.get(Constants.PREF_CONFIRM_REMOVE_LAYERS, true); 732 showWarning = removeAllLayers(showWarning); 733 store.put(Constants.PREF_CONFIRM_REMOVE_LAYERS, showWarning); 734 } 735 736 /** 737 * Handles removing all loaded layers ({@literal "displays"} in IDV-land) 738 * and data sources. 739 * 740 * <p>If {@link ArgsManager#getIsOffScreen()} is {@code true}, this method 741 * will ignore the user's preferences and remove all layers and data. 742 * 743 * @see #removeAllData(boolean) 744 * @see #removeAllLayers(boolean) 745 */ 746 public void removeAllLayersAndData() { 747 boolean reallyRemove = false; 748 boolean continueWarning = true; 749 750 if (getArgsManager().getIsOffScreen()) { 751 removeAllData(false); 752 removeAllLayers(false); 753 } 754 755 IdvObjectStore store = getStore(); 756 boolean showWarning = store.get(Constants.PREF_CONFIRM_REMOVE_BOTH, true); 757 if (showWarning) { 758 Set<WarningResult> result = showWarningDialog( 759 "Confirm Removal", 760 "This action will remove all of your currently loaded layers and data.<br>Is this what you want to do?", 761 Constants.PREF_CONFIRM_REMOVE_BOTH, 762 "Always ask?", 763 "Remove all layers and data", 764 "Do not remove anything"); 765 reallyRemove = result.contains(WarningResult.OK); 766 continueWarning = result.contains(WarningResult.SHOW); 767 } else { 768 // user doesn't want to see warning messages. 769 reallyRemove = true; 770 continueWarning = false; 771 } 772 773 // don't show the individual warning messages as the user has attempted 774 // to remove *both* 775 if (reallyRemove) { 776 removeAllData(false); 777 removeAllLayers(false); 778 } 779 780 store.put(Constants.PREF_CONFIRM_REMOVE_BOTH, continueWarning); 781 } 782 783 /** 784 * Helper method for showing the removal warning dialog. Note that none of 785 * these parameters should be {@code null} or empty. 786 * 787 * @param title Title of the warning dialog. 788 * @param message Contents of the warning. May contain HTML, but you do 789 * not need to provide opening and closing {@literal "html"} tags. 790 * @param prefId ID of the preference that controls whether or not the 791 * dialog should be displayed. 792 * @param prefLabel Brief description of the preference. 793 * @param okLabel Text of button that signals removal. 794 * @param cancelLabel Text of button that signals cancelling removal. 795 * 796 * @return A {@code Set} of {@link WarningResult}s that describes what the 797 * user opted to do. Should always contain only <b>two</b> elements. One 798 * for whether or not {@literal "ok"} or {@literal "cancel"} was clicked, 799 * and one for whether or not the warning should continue to be displayed. 800 */ 801 private Set<WarningResult> showWarningDialog(final String title, 802 final String message, final String prefId, final String prefLabel, 803 final String okLabel, final String cancelLabel) 804 { 805 JCheckBox box = new JCheckBox(prefLabel, true); 806 JComponent comp = GuiUtils.vbox( 807 new JLabel("<html>"+message+"</html>"), 808 GuiUtils.inset(box, new Insets(4, 15, 0, 10))); 809 810 Object[] options = { okLabel, cancelLabel }; 811 int result = JOptionPane.showOptionDialog( 812 LogUtil.getCurrentWindow(), // parent 813 comp, // msg 814 title, // title 815 JOptionPane.YES_NO_OPTION, // option type 816 JOptionPane.WARNING_MESSAGE, // message type 817 (Icon)null, // icon? 818 options, // selection values 819 options[1]); // initial? 820 821 WarningResult button = WarningResult.CANCEL; 822 if (result == JOptionPane.YES_OPTION) { 823 button = WarningResult.OK; 824 } 825 826 WarningResult show = WarningResult.HIDE; 827 if (box.isSelected()) { 828 show = WarningResult.SHOW; 829 } 830 831 return EnumSet.of(button, show); 832 } 833 834 public void removeTabData() { 835 } 836 837 public void removeTabLayers() { 838 839 } 840 841 public void removeTabLayersAndData() { 842 } 843 844 /** 845 * Overridden so that McIDAS-V doesn't have to create an entire new {@link ucar.unidata.idv.ui.IdvWindow} 846 * if {@link VMManager#findViewManager(ViewDescriptor)} can't find an appropriate 847 * ViewManager for {@code viewDescriptor}. 848 * 849 * <p>Not doing the above causes McIDAS-V to get stuck in a window creation 850 * loop. 851 */ 852 @Override public ViewManager getViewManager(ViewDescriptor viewDescriptor, 853 boolean newWindow, String properties) 854 { 855 ViewManager vm = 856 getVMManager().findOrCreateViewManager(viewDescriptor, properties); 857 if (vm == null) { 858 vm = super.getViewManager(viewDescriptor, newWindow, properties); 859 } 860 return vm; 861 } 862 863 /** 864 * Returns a reference to the current McIDAS-V object. Useful for working 865 * inside static methods. <b>Always check for null when using this 866 * method</b>. 867 * 868 * @return Either the current McIDAS-V "god object" or {@code null}. 869 */ 870 public static McIDASV getStaticMcv() { 871 return staticMcv; 872 } 873 874 /** 875 * @see ucar.unidata.idv.IdvBase#setIdv(ucar.unidata.idv.IntegratedDataViewer) 876 */ 877 @Override 878 public void setIdv(IntegratedDataViewer idv) { 879 this.idv = idv; 880 } 881 882 /** 883 * Load the McV properties. All other property files are disregarded. 884 * 885 * @see ucar.unidata.idv.IntegratedDataViewer#initPropertyFiles(java.util.List) 886 */ 887 @Override 888 public void initPropertyFiles(List files) { 889 files.clear(); 890 files.add(Constants.PROPERTIES_FILE); 891 } 892 893 /** 894 * Makes {@link PersistenceManager} save off a default {@literal "layout"} 895 * bundle. 896 */ 897 public void doSaveAsDefaultLayout() { 898 Misc.run(new Runnable() { 899 @Override public void run() { 900 ((PersistenceManager)getPersistenceManager()).doSaveAsDefaultLayout(); 901 } 902 }); 903 } 904 905 /** 906 * Determines whether or not a default layout exists. 907 * 908 * @return {@code true} if there is a default layout, {@code false} 909 * otherwise. 910 */ 911 public boolean hasDefaultLayout() { 912 String path = 913 getResourceManager().getResources(IdvResourceManager.RSC_BUNDLES) 914 .getWritable(); 915 return new File(path).exists(); 916 } 917 918 /** 919 * Called from the menu command to clear the default bundle. Overridden 920 * in McIDAS-V so that we reference the <i>layout</i> rather than the 921 * bundle. 922 */ 923 @Override public void doClearDefaults() { 924 if (GuiUtils.showYesNoDialog(null, 925 "Are you sure you want to delete your default layout?", 926 "Delete confirmation")) 927 resourceManager.clearDefaultBundles(); 928 } 929 930 /** 931 * <p> 932 * Overridden so that the support form becomes non-modal if launched from 933 * an exception dialog. 934 * </p> 935 * 936 * @see IntegratedDataViewer#addErrorButtons(JDialog, List, String, Throwable) 937 */ 938 @Override public void addErrorButtons(final JDialog dialog, 939 List buttonList, final String msg, final Throwable exc) 940 { 941 JButton supportBtn = new JButton("Support Form"); 942 supportBtn.addActionListener(new ActionListener() { 943 @Override public void actionPerformed(ActionEvent ae) { 944 getIdvUIManager().showSupportForm(msg, 945 LogUtil.getStackTrace(exc), null); 946 } 947 }); 948 buttonList.add(supportBtn); 949 } 950 951 /** 952 * This method is useful for storing commandline {@literal "properties"} 953 * with the user's preferences. 954 */ 955 private void overridePreferences() { 956 StateManager stateManager = (StateManager)getStateManager(); 957 int renderThreads = getMaxRenderThreadCount(); 958 stateManager.putPreference(PREF_THREADS_RENDER, renderThreads); 959 stateManager.putPreference(PREF_THREADS_DATA, getMaxDataThreadCount()); 960 visad.util.ThreadManager.setGlobalMaxThreads(renderThreads); 961 } 962 963 /** 964 * Determine if the last {@literal "exit"} was clean--whether or not 965 * {@code SESSION_FILE} was removed before the McIDAS-V process terminated. 966 * 967 * <p>If the exit was not clean, the user is prompted to submit a support 968 * request.</p> 969 */ 970 private void detectAndHandleCrash() { 971 GuiUtils.setApplicationTitle(""); 972 if (cleanExit || getArgsManager().getIsOffScreen()) { 973 return; 974 } 975 976 String msg = "The previous McIDAS-V session did not exit cleanly.<br>"+ 977 "Do you want to send the log file to the McIDAS Help Desk?"; 978 if (previousStart != null) { 979 msg = "The previous McIDAS-V session (start time: %s) did not exit cleanly.<br>"+ 980 "Do you want to send the log file to the McIDAS Help Desk?"; 981 msg = String.format(msg, previousStart); 982 } 983 984 boolean continueAsking = getStore().get("mcv.crash.boom.send.report", true); 985 if (!continueAsking) { 986 return; 987 } 988 989 Set<WarningResult> result = showWarningDialog( 990 "Report Crash", 991 msg, 992 "mcv.crash.boom.send.report", 993 "Always ask?", 994 "Open support form", 995 "Do not report"); 996 997 getStore().put("mcv.crash.boom.send.report", result.contains(WarningResult.SHOW)); 998 if (!result.contains(WarningResult.OK)) { 999 return; 1000 } 1001 1002 getIdvUIManager().showSupportForm(); 1003 } 1004 1005 /** 1006 * Called after the IDV has finished setting everything up after starting. 1007 * McIDAS-V is currently only using this method to determine if the last 1008 * {@literal "exit"} was clean--whether or not {@code SESSION_FILE} was 1009 * removed before the McIDAS-V process terminated. 1010 * 1011 * Called after the IDV has finished setting everything up. McIDAS-V uses 1012 * this method to handle: 1013 * 1014 * <ul> 1015 * <li>Clearing out the automatic display creation arguments.</li> 1016 * <li>Presence of certain properties on the commandline.</li> 1017 * <li>Detection and handling of a crashed McIDAS-V session (searching for {@code SESSION_FILE}.</li> 1018 * </ul> 1019 * 1020 * @see ArgumentManager#clearAutomaticDisplayArgs() 1021 * @see #overridePreferences() 1022 * @see #detectAndHandleCrash() 1023 */ 1024 @Override public void initDone() { 1025 ((ArgumentManager)argsManager).clearAutomaticDisplayArgs(); 1026 1027 overridePreferences(); 1028 1029 detectAndHandleCrash(); 1030 1031 estimate = System.nanoTime() - startTime; 1032 logger.info("estimated startup duration: {} ms", estimate / 1e6); 1033 } 1034 1035 /** 1036 * @see IntegratedDataViewer#doOpen(String, boolean, boolean) 1037 */ 1038 @Override public void doOpen(final String filename, 1039 final boolean checkUserPreference, final boolean andRemove) 1040 { 1041 doOpenInThread(filename, checkUserPreference, andRemove); 1042 } 1043 1044 /** 1045 * Have the user select a bundle. If andRemove is true then we remove all data sources and displays. 1046 * 1047 * Then we open the bundle and start doing unpersistence things. 1048 * 1049 * @param filename The filename to open 1050 * @param checkUserPreference Should we show, if needed, the {@literal "open"} dialog 1051 * @param andRemove If true then first remove all data sources and displays 1052 */ 1053 private void doOpenInThread(String filename, boolean checkUserPreference, 1054 boolean andRemove) 1055 { 1056 boolean overwriteData = false; 1057 if (filename == null) { 1058 if (overwriteDataCbx.getToolTipText() == null) { 1059 overwriteDataCbx.setToolTipText("Change the file paths that the data sources use"); 1060 } 1061 1062 filename = FileManager.getReadFile("Open File", 1063 ((ArgumentManager)getArgsManager()).getBundleFilters(true), 1064 GuiUtils.top(overwriteDataCbx)); 1065 1066 if (filename == null) { 1067 return; 1068 } 1069 1070 overwriteData = overwriteDataCbx.isSelected(); 1071 } 1072 1073 if (ArgumentManager.isXmlBundle(filename)) { 1074 getPersistenceManager().decodeXmlFile(filename, 1075 checkUserPreference, overwriteData); 1076 return; 1077 } 1078 handleAction(filename, null); 1079 } 1080 1081 /** 1082 * Factory method to create the McIDAS-V @link JythonManager}. 1083 * 1084 * @return New {@code JythonManager}. 1085 */ 1086 @Override protected JythonManager doMakeJythonManager() { 1087 logger.debug("returning a new JythonManager"); 1088 return new JythonManager(this); 1089 } 1090 1091 /** 1092 * Factory method to create a McIDAS-V {@link McIdasChooserManager}. Here we create our own manager so it can do 1093 * McV specific things. 1094 * 1095 * @return The UI manager indicated by the startup properties. 1096 * 1097 * @see ucar.unidata.idv.IdvBase#doMakeIdvChooserManager() 1098 */ 1099 @Override 1100 protected IdvChooserManager doMakeIdvChooserManager() { 1101 chooserManager = (McIdasChooserManager)makeManager( 1102 McIdasChooserManager.class, new Object[] { this }); 1103 chooserManager.init(); 1104 return chooserManager; 1105 } 1106 1107 /** 1108 * Factory method to create the {@link IdvUIManager}. Here we create our own UI manager so it can do McV 1109 * specific things. 1110 * 1111 * @return {@link UIManager} indicated by the startup properties. 1112 * 1113 * @see ucar.unidata.idv.IdvBase#doMakeIdvUIManager() 1114 */ 1115 @Override 1116 protected IdvUIManager doMakeIdvUIManager() { 1117 return new UIManager(this); 1118 } 1119 1120 /** 1121 * Create our own VMManager so that we can make the tabs play nice. 1122 * @see ucar.unidata.idv.IdvBase#doMakeVMManager() 1123 */ 1124 @Override 1125 protected VMManager doMakeVMManager() { 1126 // what an ugly class name :( 1127 return new ViewManagerManager(this); 1128 } 1129 1130 /** 1131 * Make the {@link McIdasPreferenceManager}. 1132 * @see ucar.unidata.idv.IdvBase#doMakePreferenceManager() 1133 */ 1134 @Override 1135 protected IdvPreferenceManager doMakePreferenceManager() { 1136 return new McIdasPreferenceManager(this); 1137 } 1138 1139 /** 1140 * <p>McIDAS-V (alpha 10+) needs to handle both IDV bundles without 1141 * component groups and all bundles from prior McV alphas. You better 1142 * believe we need to extend the persistence manager functionality!</p> 1143 * 1144 * @see ucar.unidata.idv.IdvBase#doMakePersistenceManager() 1145 */ 1146 @Override protected IdvPersistenceManager doMakePersistenceManager() { 1147 return new PersistenceManager(this); 1148 } 1149 1150 /** 1151 * Create, if needed, and return the {@link McIdasChooserManager}. 1152 * 1153 * @return The Chooser manager 1154 */ 1155 public McIdasChooserManager getMcIdasChooserManager() { 1156 return (McIdasChooserManager)getIdvChooserManager(); 1157 } 1158 1159 /** 1160 * Returns the {@link MonitorManager}. 1161 * 1162 * @return McIDAS-V {@literal "monitor manager"}. 1163 */ 1164 public MonitorManager getMonitorManager() { 1165 return monitorManager; 1166 } 1167 1168 /** 1169 * Responds to events generated by the server manager's GUI. Currently 1170 * limited to {@link edu.wisc.ssec.mcidasv.servermanager.TabbedAddeManager.Event#CLOSED TabbedAddeManager.Event#CLOSED}. 1171 * 1172 * @param evt {@code TabbedAddeManager} event to respond to. 1173 */ 1174 @EventSubscriber(eventClass=TabbedAddeManager.Event.class) 1175 public void onServerManagerWindowEvent(TabbedAddeManager.Event evt) { 1176 if (evt == TabbedAddeManager.Event.CLOSED) { 1177 tabbedAddeManager = null; 1178 } 1179 } 1180 1181 /** 1182 * Creates (if needed) the server manager GUI and displays it. 1183 */ 1184 public void showServerManager() { 1185 if (tabbedAddeManager == null) { 1186 tabbedAddeManager = new TabbedAddeManager(getServerManager()); 1187 } 1188 tabbedAddeManager.showManager(); 1189 } 1190 1191 /** 1192 * Creates a new server manager (if needed) and returns it. 1193 * 1194 * @return The McIDAS-V ADDE server manager. 1195 */ 1196 public EntryStore getServerManager() { 1197 if (addeEntries == null) { 1198 addeEntries = new EntryStore(getStore(), getResourceManager()); 1199 addeEntries.startLocalServer(); 1200 } 1201 return addeEntries; 1202 } 1203 1204 public McvDataManager getMcvDataManager() { 1205 return (McvDataManager)getDataManager(); 1206 } 1207 1208 /** 1209 * Get McIDASV. 1210 * @see ucar.unidata.idv.IdvBase#getIdv() 1211 */ 1212 @Override public IntegratedDataViewer getIdv() { 1213 return this; 1214 } 1215 1216 /** 1217 * Creates a McIDAS-V argument manager so that McV can handle some non-IDV 1218 * command line things. 1219 * 1220 * @param args The arguments from the command line. 1221 * 1222 * @see ucar.unidata.idv.IdvBase#doMakeArgsManager(java.lang.String[]) 1223 */ 1224 @Override protected ArgsManager doMakeArgsManager(String[] args) { 1225 return new ArgumentManager(this, args); 1226 } 1227 1228 /** 1229 * Factory method to create the {@link McvDataManager}. 1230 * 1231 * @return The data manager 1232 * 1233 * @see ucar.unidata.idv.IdvBase#doMakeDataManager() 1234 */ 1235 @Override protected DataManager doMakeDataManager() { 1236 return new McvDataManager(this); 1237 } 1238 1239 /** 1240 * Make the McIDAS-V {@link StateManager}. 1241 * @see ucar.unidata.idv.IdvBase#doMakeStateManager() 1242 */ 1243 @Override protected StateManager doMakeStateManager() { 1244 return new StateManager(this); 1245 } 1246 1247 /** 1248 * Make the McIDAS-V {@link ResourceManager}. 1249 * @see ucar.unidata.idv.IdvBase#doMakeResourceManager() 1250 */ 1251 @Override protected IdvResourceManager doMakeResourceManager() { 1252 return new ResourceManager(this); 1253 } 1254 1255 /** 1256 * Make the {@link McIdasColorTableManager}. 1257 * @see ucar.unidata.idv.IdvBase#doMakeColorTableManager() 1258 */ 1259 @Override protected ColorTableManager doMakeColorTableManager() { 1260 return new McIdasColorTableManager(); 1261 } 1262 1263 /** 1264 * Factory method to create the {@link McvPluginManager}. 1265 * 1266 * @return The McV plugin manager. 1267 * 1268 * @see ucar.unidata.idv.IdvBase#doMakePluginManager() 1269 */ 1270 @Override protected PluginManager doMakePluginManager() { 1271 return new McvPluginManager(this); 1272 } 1273 1274// /** 1275// * Make the {@link edu.wisc.ssec.mcidasv.data.McIDASVProjectionManager}. 1276// * @see ucar.unidata.idv.IdvBase#doMakeIdvProjectionManager() 1277// */ 1278// @Override 1279// protected IdvProjectionManager doMakeIdvProjectionManager() { 1280// return new McIDASVProjectionManager(this); 1281// } 1282 1283 /** 1284 * Make a help button for a particular help topic 1285 * 1286 * @param helpId the topic id 1287 * @param toolTip the tooltip 1288 * 1289 * @return the button 1290 */ 1291 @Override public JComponent makeHelpButton(String helpId, String toolTip) { 1292 JButton btn = McVGuiUtils.makeImageButton(Constants.ICON_HELP, 1293 getIdvUIManager(), "showHelp", helpId, "Show help"); 1294 1295 if (toolTip != null) { 1296 btn.setToolTipText(toolTip); 1297 } 1298 return btn; 1299 } 1300 1301 /** 1302 * Return the current {@literal "userpath"}. 1303 * 1304 * @return Path to the user's {@literal "McIDAS-V directory"}. 1305 */ 1306 public String getUserDirectory() { 1307 return StartupManager.getInstance().getPlatform().getUserDirectory(); 1308 } 1309 1310 /** 1311 * Return the path to a file within {@literal "userpath"}. 1312 * 1313 * @param filename File within the userpath. 1314 * 1315 * @return Path to a file within the user's {@literal "McIDAS-V directory"}. 1316 * No path validation is performed, so please be aware that the returned 1317 * path may not exist. 1318 */ 1319 public String getUserFile(String filename) { 1320 return StartupManager.getInstance().getPlatform().getUserFile(filename); 1321 } 1322 1323 /** 1324 * Invokes the main method for a given class. 1325 * 1326 * <p>Note: this is rather limited so far as it doesn't pass in any arguments. 1327 * 1328 * @param className Class whose main method is to be invoked. Cannot be {@code null}. 1329 */ 1330 public void runPluginMainMethod(final String className) { 1331 final String[] dummyArgs = { "" }; 1332 try { 1333 Class<?> clazz = Misc.findClass(className); 1334 Method mainMethod = Misc.findMethod(clazz, "main", new Class[] { dummyArgs.getClass() }); 1335 if (mainMethod != null) { 1336 mainMethod.invoke(null, new Object[] { dummyArgs }); 1337 } 1338 } catch (Exception e) { 1339 logger.error("problem with plugin class", e); 1340 LogUtil.logException("problem running main method for class: "+className, e); 1341 } 1342 } 1343 1344 /** 1345 * Attempts to determine if a given string is a 1346 * {@literal "loopback address"} (aka localhost). 1347 * 1348 * <p>Strings are <b>trimmed and converted to lowercase</b>, and currently 1349 * checked against: 1350 * <ul> 1351 * <li>{@code 127.0.0.1}</li> 1352 * <li>{@code ::1} (for IPv6)</li> 1353 * <li>Strings starting with {@code localhost}.</li> 1354 * </ul> 1355 * 1356 * @param host {@code String} to check. Should not be {@code null}. 1357 * 1358 * @return {@code true} if {@code host} is a recognized loopback address. 1359 * {@code false} otherwise. 1360 * 1361 * @throws NullPointerException if {@code host} is {@code null}. 1362 */ 1363 public static boolean isLoopback(final String host) { 1364 String cleaned = Objects.requireNonNull(host.trim().toLowerCase()); 1365 return "127.0.0.1".startsWith(cleaned) 1366 || "::1".startsWith(cleaned) 1367 || cleaned.startsWith("localhost"); 1368 } 1369 1370 /** 1371 * Are we on a Mac? Used to build the MRJ handlers, taken from TN2110. 1372 * 1373 * @return {@code true} if this session is running on top of OS X, {@code false} 1374 * otherwise. 1375 * 1376 * @see <a href="http://developer.apple.com/technotes/tn2002/tn2110.html">TN2110</a> 1377 */ 1378 public static boolean isMac() { 1379 String osName = System.getProperty("os.name"); 1380 return osName.contains("OS X"); 1381 } 1382 1383 /** 1384 * Queries the {@code os.name} system property and if the result does not 1385 * start with {@literal "Windows"}, the platform is assumed to be 1386 * {@literal "unix-like"}. 1387 * 1388 * <p>Given the McIDAS-V supported platforms (Windows, {@literal "Unix"}, 1389 * and OS X), the above logic is safe. 1390 * 1391 * @return {@code true} if we're not running on Windows, {@code false} 1392 * otherwise. 1393 * 1394 * @throws RuntimeException if there is no property associated with 1395 * {@code os.name}. 1396 */ 1397 public static boolean isUnixLike() { 1398 String osName = System.getProperty("os.name"); 1399 if (osName == null) { 1400 throw new RuntimeException("no os.name system property!"); 1401 } 1402 1403 if (System.getProperty("os.name").startsWith("Windows")) { 1404 return false; 1405 } 1406 return true; 1407 } 1408 1409 /** 1410 * Queries the {@code os.name} system property and if the result starts 1411 * with {@literal "Windows"}, the platform is assumed to be Windows. Duh. 1412 * 1413 * @return {@code true} if we're running on Windows, {@code false} 1414 * otherwise. 1415 * 1416 * @throws RuntimeException if there is no property associated with 1417 * {@code os.name}. 1418 */ 1419 public static boolean isWindows() { 1420 String osName = System.getProperty("os.name"); 1421 if (osName == null) { 1422 throw new RuntimeException("no os.name system property!"); 1423 } 1424 1425 return osName.startsWith("Windows"); 1426 } 1427 1428 /** 1429 * If McIDAS-V is running on Windows, this method will return a 1430 * {@code String} that looks like {@literal "C:"} or {@literal "D:"}, etc. 1431 * 1432 * <p>If McIDAS-V is not running on Windows, this method will return an 1433 * empty {@code String}. 1434 * 1435 * @return Either the {@literal "drive letter"} of the {@code java.home} 1436 * property or an empty {@code String} if McIDAS-V isn't running on Windows. 1437 * 1438 * @throws RuntimeException if there is no property associated with 1439 * {@code java.home}. 1440 */ 1441 public static String getJavaDriveLetter() { 1442 if (!isWindows()) { 1443 return ""; 1444 } 1445 1446 String home = System.getProperty("java.home"); 1447 if (home == null) { 1448 throw new RuntimeException("no java.home system property!"); 1449 } 1450 1451 return home.substring(0, 2); 1452 } 1453 1454 /** 1455 * Attempts to create a {@literal "session"} file. This method will create 1456 * a {@literal "userpath"} if it does not already exist. 1457 * 1458 * @param path Path of the session file that should get created. 1459 * {@code null} values are not allowed, and sufficient priviledges are 1460 * assumed. 1461 * 1462 * @throws AssertionError if McIDAS-V couldn't write to {@code path}. 1463 * 1464 * @see #SESSION_FILE 1465 * @see #hadCleanExit(String) 1466 * @see #removeSessionFile(String) 1467 */ 1468 private static void createSessionFile(final String path) { 1469 assert path != null : "Cannot create a null path"; 1470 FileOutputStream out = null; 1471 PrintStream p = null; 1472 1473 File dir = new File(StartupManager.getInstance().getPlatform().getUserDirectory()); 1474 if (!dir.exists()) { 1475 dir.mkdir(); 1476 } 1477 1478 try { 1479 out = new FileOutputStream(path); 1480 p = new PrintStream(out); 1481 p.println(new Date().getTime()); 1482 } catch (Exception e) { 1483 throw new AssertionError("Could not write to "+path+". Error message: "+e.getMessage()); 1484 } finally { 1485 if (p != null) { 1486 p.close(); 1487 } 1488 if (out != null) { 1489 try { 1490 out.close(); 1491 } catch (IOException e) { 1492 throw new AssertionError("Could not close "+path+". Error message: "+e.getMessage()); 1493 } 1494 } 1495 } 1496 } 1497 1498 /** 1499 * Attempts to extract a timestamp from {@code path}. {@code path} is 1500 * expected to <b>only</b> contain a single line consisting of a 1501 * {@link Long} integer. 1502 * 1503 * @param path Path to the file of interest. 1504 * 1505 * @return Either a {@link Date} of the timestamp contained in 1506 * {@code path} or {@code null} if the extraction failed. 1507 */ 1508 private static Date extractDate(final String path) { 1509 assert path != null; 1510 Date savedDate = null; 1511 BufferedReader reader = null; 1512 try { 1513 reader = new BufferedReader(new FileReader(path)); 1514 String line = reader.readLine(); 1515 if (line != null) { 1516 savedDate = new Date(Long.parseLong(line.trim())); 1517 } 1518 } catch (Exception e) { 1519 logger.trace("problem extracting the date!", e); 1520 } finally { 1521 if (reader != null) { 1522 try { 1523 reader.close(); 1524 } catch (IOException e) { 1525 logger.trace("problem closing session file!", e); 1526 } 1527 } 1528 } 1529 return savedDate; 1530 } 1531 1532 /** 1533 * Attempts to remove the file accessible via {@code path}. 1534 * 1535 * @param path Path of the file that'll get removed. This should be 1536 * non-null and point to an existing and writable filename (not a 1537 * directory). 1538 * 1539 * @throws AssertionError if the file at {@code path} could not be 1540 * removed. 1541 * 1542 * @see #SESSION_FILE 1543 * @see #createSessionFile(String) 1544 * @see #hadCleanExit(String) 1545 */ 1546 private static void removeSessionFile(final String path) { 1547 if (path == null) { 1548 return; 1549 } 1550 1551 File f = new File(path); 1552 1553 if (!f.exists() || !f.canWrite() || f.isDirectory()) { 1554 return; 1555 } 1556 1557 if (!f.delete()) { 1558 throw new AssertionError("Could not delete session file"); 1559 } 1560 } 1561 1562 /** 1563 * Tries to determine whether or not the last McIDAS-V session ended 1564 * {@literal "cleanly"}. Currently a simple check for a 1565 * {@literal "session"} file that is created upon starting and removed upon 1566 * ending. 1567 * 1568 * @param path Path to the session file to check. Can't be {@code null}. 1569 * 1570 * @return Either {@code true} if the file pointed at by {@code path} does 1571 * <b><i>NOT</i></b> exist, {@code false} if it does exist. 1572 * 1573 * @see #SESSION_FILE 1574 * @see #createSessionFile(String) 1575 * @see #removeSessionFile(String) 1576 */ 1577 private static boolean hadCleanExit(final String path) { 1578 assert path != null : "Cannot test for a null path"; 1579 return !(new File(path).exists()); 1580 } 1581 1582 /** 1583 * Returns the (<i>current</i>) path to the session file. Note that the 1584 * location of the file may change arbitrarily. 1585 * 1586 * @return {@code String} pointing to the session file. 1587 * 1588 * @see #SESSION_FILE 1589 */ 1590 public static String getSessionFilePath() { 1591 return StartupManager.getInstance().getPlatform().getUserFile("session.tmp"); 1592 } 1593 1594 /** 1595 * Useful for providing the startup manager with values other than the 1596 * defaults... Note that this method attempts to update the value of 1597 * {@link #SESSION_FILE}. 1598 * 1599 * @param args Likely the argument array coming from the main method. 1600 */ 1601 private static void applyArgs(final String[] args) { 1602 assert args != null : "Cannot use a null argument array"; 1603 StartupManager.applyArgs(true, false, args); 1604 SESSION_FILE = getSessionFilePath(); 1605 } 1606 1607 /** 1608 * This returns the set of {@link ControlDescriptor}s 1609 * that can be shown. The ordering of this list determines the 1610 * "default" controls shown in the Field Selector, so we override 1611 * here for control over the ordering. 1612 * 1613 * @param includeTemplates If true then include the display templates 1614 * @return re-ordered List of shown control descriptors 1615 */ 1616 @Override public List getControlDescriptors(boolean includeTemplates) { 1617 List l = super.getControlDescriptors(includeTemplates); 1618 for (int i = 0; i < l.size(); i++) { 1619 ControlDescriptor cd = (ControlDescriptor) l.get(i); 1620 if (cd.getControlId().equals("omni")) { 1621 // move the omni control to the end of the list 1622 // so it will never be "default" in Field Selector 1623 l.remove(i); 1624 l.add(cd); 1625 } 1626 } 1627 return l; 1628 } 1629 1630 /** 1631 * The main. Configure the logging and create the McIdasV 1632 * 1633 * @param args Command line arguments 1634 * 1635 * @throws Exception When something untoward happens 1636 */ 1637 public static void main(String[] args) throws Exception { 1638 startTime = System.nanoTime(); 1639 1640 try { 1641 applyArgs(args); 1642 1643 SysOutOverSLF4J.sendSystemOutAndErrToSLF4J(LogLevel.INFO, LogLevel.WARN); 1644 1645 // Optionally remove existing handlers attached to j.u.l root logger 1646 SLF4JBridgeHandler.removeHandlersForRootLogger(); // (since SLF4J 1.6.5) 1647 1648 // add SLF4JBridgeHandler to j.u.l's root logger, should be done once during 1649 // the initialization phase of your application 1650 SLF4JBridgeHandler.install(); 1651 1652 LogUtil.configure(); 1653 1654 long sysMem = Long.valueOf(SystemState.queryOpSysProps().get("opsys.memory.physical.total")); 1655 logger.info("============================================================================="); 1656 logger.info("Starting McIDAS-V @ {}", new Date()); 1657 logger.info("Versions:"); 1658 logger.info("{}", SystemState.getMcvVersionString()); 1659 logger.info("{}", SystemState.getIdvVersionString()); 1660 logger.info("{}", SystemState.getVisadVersionString()); 1661 logger.info("{} MB system memory", Math.round(sysMem/1024/1024)); 1662 1663 if (!hadCleanExit(SESSION_FILE)) { 1664 previousStart = extractDate(SESSION_FILE); 1665 } 1666 1667 createSessionFile(SESSION_FILE); 1668 1669 McIDASV myself = new McIDASV(args); 1670 } catch (IllegalArgumentException e) { 1671 String msg = "McIDAS-V could not initialize itself. "; 1672 String osName = System.getProperty("os.name"); 1673 if (osName.contains("Windows")) { 1674 LogUtil.userErrorMessage(msg+'\n'+e.getMessage()); 1675 } else { 1676 System.err.println(msg+e.getMessage()); 1677 } 1678 1679 } 1680 } 1681 1682 /** 1683 * Attempts a clean shutdown of McIDAS-V. Currently this entails 1684 * suppressing any error dialogs, explicitly killing the 1685 * {@link #addeEntries}, and removing {@link #SESSION_FILE}. 1686 * 1687 * @param exitCode System exit code to use. 1688 * 1689 * @see IntegratedDataViewer#quit() 1690 */ 1691 @Override protected void exit(int exitCode) { 1692 LogUtil.setShowErrorsInGui(false); 1693 1694 if (addeEntries != null) { 1695 addeEntries.saveForShutdown(); 1696 addeEntries.stopLocalServer(); 1697 } 1698 1699 removeSessionFile(SESSION_FILE); 1700 logger.info("Exiting McIDAS-V @ {}", new Date()); 1701 System.exit(exitCode); 1702 } 1703 1704 /** 1705 * Exposes {@link #exit(int)} to other classes. 1706 * 1707 * @param exitCode System exit code to use. 1708 * 1709 * @see #exit(int) 1710 */ 1711 public void exitMcIDASV(int exitCode) { 1712 exit(exitCode); 1713 } 1714}