001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2018 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 java.awt.Insets; 032import java.awt.Rectangle; 033import java.awt.event.ActionEvent; 034import java.awt.event.ActionListener; 035import java.io.BufferedReader; 036import java.io.File; 037import java.io.FileOutputStream; 038import java.io.FileReader; 039import java.io.IOException; 040import java.util.ArrayList; 041import java.util.Collection; 042import java.util.Collections; 043import java.util.Enumeration; 044import java.util.Hashtable; 045import java.util.LinkedHashMap; 046import java.util.List; 047import java.util.Map; 048import java.util.Set; 049import java.util.zip.ZipEntry; 050import java.util.zip.ZipInputStream; 051 052import javax.swing.JCheckBox; 053import javax.swing.JComboBox; 054import javax.swing.JComponent; 055import javax.swing.JLabel; 056import javax.swing.JOptionPane; 057import javax.swing.JPanel; 058import javax.swing.JRadioButton; 059import javax.swing.JTextField; 060 061import edu.wisc.ssec.mcidasv.startupmanager.StartupManager; 062import edu.wisc.ssec.mcidasv.startupmanager.options.FileOption; 063import edu.wisc.ssec.mcidasv.startupmanager.options.OptionMaster; 064import org.python.core.PyObject; 065import org.slf4j.Logger; 066import org.slf4j.LoggerFactory; 067import org.w3c.dom.Document; 068import org.w3c.dom.Element; 069import org.w3c.dom.Node; 070 071import ucar.unidata.data.DataChoice; 072import ucar.unidata.data.DataSource; 073import ucar.unidata.data.DataSourceDescriptor; 074import ucar.unidata.data.DataSourceImpl; 075import ucar.unidata.idv.DisplayControl; 076import ucar.unidata.idv.IdvManager; 077import ucar.unidata.idv.IdvObjectStore; 078import ucar.unidata.idv.IdvPersistenceManager; 079import ucar.unidata.idv.IdvResourceManager; 080import ucar.unidata.idv.IntegratedDataViewer; 081import ucar.unidata.idv.MapViewManager; 082import ucar.unidata.idv.SavedBundle; 083import ucar.unidata.idv.ServerUrlRemapper; 084import ucar.unidata.idv.ViewDescriptor; 085import ucar.unidata.idv.ViewManager; 086import ucar.unidata.idv.control.DisplayControlImpl; 087import ucar.unidata.idv.ui.IdvComponentGroup; 088import ucar.unidata.idv.ui.IdvComponentHolder; 089import ucar.unidata.idv.ui.IdvUIManager; 090import ucar.unidata.idv.ui.IdvWindow; 091import ucar.unidata.idv.ui.IdvXmlUi; 092import ucar.unidata.idv.ui.LoadBundleDialog; 093import ucar.unidata.idv.ui.WindowInfo; 094import ucar.unidata.ui.ComponentGroup; 095import ucar.unidata.util.ColorTable; 096import ucar.unidata.util.FileManager; 097import ucar.unidata.util.GuiUtils; 098import ucar.unidata.util.IOUtil; 099import ucar.unidata.util.LogUtil; 100import ucar.unidata.util.Misc; 101import ucar.unidata.util.PollingInfo; 102import ucar.unidata.util.StringUtil; 103import ucar.unidata.util.Trace; 104import ucar.unidata.util.TwoFacedObject; 105import ucar.unidata.xml.XmlEncoder; 106import ucar.unidata.xml.XmlResourceCollection; 107 108import edu.wisc.ssec.mcidasv.control.ImagePlanViewControl; 109import edu.wisc.ssec.mcidasv.probes.ReadoutProbe; 110import edu.wisc.ssec.mcidasv.ui.McvComponentGroup; 111import edu.wisc.ssec.mcidasv.ui.McvComponentHolder; 112import edu.wisc.ssec.mcidasv.ui.UIManager; 113import edu.wisc.ssec.mcidasv.util.McVGuiUtils; 114import edu.wisc.ssec.mcidasv.util.XPathUtils; 115import edu.wisc.ssec.mcidasv.util.XmlUtil; 116 117/** 118 * McIDAS-V has 99 problems, and bundles are several of 'em. Since the UI of 119 * alpha 10 relies upon component groups and McIDAS-V needs to support IDV and 120 * bundles prior to alpha 10, we must add facilities for coping with bundles 121 * that may not contain component groups. Here's a list of the issues and how 122 * they are resolved: 123 * 124 * <ul> 125 * <li>Bundles prior to alpha 9 use the {@code TabbedUIManager}. Each tab 126 * is, internally, an IDV window. This is reflected in the contents of bundles, 127 * so the IDV wants to create a new window for each tab upon loading. Alpha 10 128 * allows the user to force bundles to only create one window. This work is 129 * done in {@link #injectComponentGroups(List)}.</li> 130 * 131 * <li>The IDV allows users to save bundles that contain <i>both</i> 132 * {@link ViewManager ViewManagers} with component groups and without! 133 * This is actually only a problem when limiting the windows; 134 * {@code injectComponentGroups} has to wrap ViewManagers without 135 * component groups in dynamic skins. These ViewManagers must be removed 136 * from the bundle's internal list of ViewManagers, as they don't exist until 137 * the dynamic skin is built. <i>Do not simply clear the list!</i> The 138 * ViewManagers within component groups must appear in it, otherwise the IDV 139 * does not add them to the {@link ucar.unidata.idv.VMManager}. If limiting 140 * windows is off, everything will be caught properly by the unpersisting 141 * facilities in {@link edu.wisc.ssec.mcidasv.ui.UIManager}.</li> 142 * </ul> 143 * 144 * @see IdvPersistenceManager 145 * @see UIManager 146 */ 147public class PersistenceManager extends IdvPersistenceManager { 148 149 /** Key used to access a bundle's McIDAS-V in-depth versioning info section. */ 150 public static final String ID_MCV_VERSION = "mcvversion"; 151 152 private static final Logger logger = LoggerFactory.getLogger(PersistenceManager.class); 153 154 /** 155 * Macro used as a place holder for wherever the IDV decides to place 156 * extracted contents of a bundle. 157 */ 158 public static final String MACRO_ZIDVPATH = '%'+PROP_ZIDVPATH+'%'; 159 160 static ucar.unidata.util.LogUtil.LogCategory log_ = 161 ucar.unidata.util.LogUtil.getLogInstance(IdvManager.class.getName()); 162 163 /** Is the bundle being saved a layout bundle? */ 164 private boolean savingDefaultLayout = false; 165 166 /** Stores the last active ViewManager from <i>before</i> a bundle load. */ 167 private ViewManager lastBeforeBundle = null; 168 169 /** 170 * Whether or not the user wants to attempt merging bundled layers into 171 * current displays. 172 */ 173 private boolean mergeBundledLayers = false; 174 175 /** Whether or not a bundle is actively loading. */ 176 private boolean bundleLoading = false; 177 178 /** Cache the parameter sets XML */ 179 private XmlResourceCollection parameterSets; 180 private static Document parameterSetsDocument; 181 private static Element parameterSetsRoot; 182 private static final String TAG_FOLDER = "folder"; 183 private static final String TAG_DEFAULT = "default"; 184 private static final String ATTR_NAME = "name"; 185 186 /** Use radio buttons to control state saving */ 187 private JRadioButton layoutOnlyRadio; 188 private JRadioButton layoutSourcesRadio; 189 private JRadioButton layoutSourcesDataRadio; 190 191 /** 192 * Java requires this constructor. 193 */ 194 public PersistenceManager() { 195 this(null); 196 } 197 198 /** 199 * Create a new persistence manager. 200 * 201 * @param idv Reference back to the application session. 202 * Cannot be {@code null}. 203 * 204 * @see ucar.unidata.idv.IdvPersistenceManager#IdvPersistenceManager(IntegratedDataViewer) 205 */ 206 public PersistenceManager(IntegratedDataViewer idv) { 207 super(idv); 208 209 //TODO: Saved for future development 210/** 211 layoutOnlyRadio = new JRadioButton("Layout only"); 212 layoutOnlyRadio.addActionListener(new ActionListener() { 213 public void actionPerformed(final ActionEvent e) { 214 saveJythonBox.setSelectedIndex(0); 215 saveJython = false; 216 makeDataRelativeCbx.setSelected(false); 217 makeDataRelative = false; 218 saveDataSourcesCbx.setSelected(false); 219 saveDataSources = false; 220 saveDataCbx.setSelected(false); 221 saveData = false; 222 } 223 }); 224 225 layoutSourcesRadio = new JRadioButton("Layout & Data Sources"); 226 layoutSourcesRadio.addActionListener(new ActionListener() { 227 public void actionPerformed(final ActionEvent e) { 228 saveJythonBox.setSelectedIndex(1); 229 saveJython = true; 230 makeDataRelativeCbx.setSelected(false); 231 makeDataRelative = false; 232 saveDataSourcesCbx.setSelected(true); 233 saveDataSources = true; 234 saveDataCbx.setSelected(false); 235 saveData = false; 236 } 237 }); 238 239 layoutSourcesDataRadio = new JRadioButton("Layout, Data Sources & Data"); 240 layoutSourcesRadio.addActionListener(new ActionListener() { 241 public void actionPerformed(final ActionEvent e) { 242 saveJythonBox.setSelectedIndex(1); 243 saveJython = true; 244 makeDataRelativeCbx.setSelected(false); 245 makeDataRelative = false; 246 saveDataSourcesCbx.setSelected(true); 247 saveDataSources = true; 248 saveDataCbx.setSelected(true); 249 saveData = true; 250 } 251 }); 252 //Group the radio buttons. 253 layoutSourcesRadio.setSelected(true); 254 ButtonGroup group = new ButtonGroup(); 255 group.add(layoutOnlyRadio); 256 group.add(layoutSourcesRadio); 257 group.add(layoutSourcesDataRadio); 258*/ 259 260 } 261 262 /** 263 * Returns the last active {@link ViewManager} from <i>before</i> loading 264 * the most recent bundle. 265 * 266 * @return Either the ViewManager or {@code null} if there was no previous 267 * ViewManager (such as loading a default bundle/layout). 268 */ 269 public ViewManager getLastViewManager() { 270 return lastBeforeBundle; 271 } 272 273 /** 274 * Returns whether or not a bundle is currently being loaded. 275 * 276 * @return Either {@code true} if {@code instantiateFromBundle} is doing 277 * what it needs to do, or {@code false}. 278 * 279 * @see #instantiateFromBundle(Hashtable, boolean, LoadBundleDialog, boolean, Hashtable, boolean, boolean, boolean) 280 */ 281 public boolean isBundleLoading() { 282 return bundleLoading; 283 } 284 285 public boolean getMergeBundledLayers() { 286 logger.trace("mergeBundledLayers={}", mergeBundledLayers); 287 return mergeBundledLayers; 288 } 289 290 private void setMergeBundledLayers(final boolean newValue) { 291 logger.trace("old={} new={}", mergeBundledLayers, newValue); 292 mergeBundledLayers = newValue; 293 } 294 295 @Override public boolean getSaveDataSources() { 296 boolean result = false; 297 if (!savingDefaultLayout) { 298 result = super.getSaveDataSources(); 299 } 300 logger.trace("getSaveDataSources={} savingDefaultLayout={}", result, savingDefaultLayout); 301 return result; 302 } 303 304 @Override public boolean getSaveDisplays() { 305 boolean result = false; 306 if (!savingDefaultLayout) { 307 result = super.getSaveDisplays(); 308 } 309 logger.trace("getSaveDisplays={} savingDefaultLayout={}", result, savingDefaultLayout); 310 return result; 311 } 312 313 @Override public boolean getSaveViewState() { 314 boolean result = true; 315 if (!savingDefaultLayout) { 316 result = super.getSaveViewState(); 317 } 318 logger.trace("getSaveViewState={} savingDefaultLayout={}", result, savingDefaultLayout); 319 return result; 320 } 321 322 @Override public boolean getSaveJython() { 323 boolean result = false; 324 if (!savingDefaultLayout) { 325 result = super.getSaveJython(); 326 } 327 logger.trace("getSaveJython={} savingDefaultLayout={}", result, savingDefaultLayout); 328 return result; 329 } 330 331 public void doSaveAsDefaultLayout() { 332 String layoutFile = getResourceManager().getResources(IdvResourceManager.RSC_BUNDLES).getWritable(); 333 // do prop check here? 334 File f = new File(layoutFile); 335 if (f.exists()) { 336 boolean result = GuiUtils.showYesNoDialog(null, "Saving a new default layout will overwrite your existing default layout. Do you wish to continue?", "Overwrite Confirmation"); 337 if (!result) { 338 return; 339 } 340 } 341 342 savingDefaultLayout = true; 343 try { 344 String xml = getBundleXml(true, true); 345 if (xml != null) { 346 IOUtil.writeFile(layoutFile, xml); 347 } 348 } catch (Exception e) { 349 logger.error("error while saving default layout", e); 350 } finally { 351 savingDefaultLayout = false; 352 } 353 } 354 355 @Override public JPanel getFileAccessory() { 356 // Always save displays and data sources 357 saveDisplaysCbx.setSelected(true); 358 saveDisplays = true; 359 saveViewStateCbx.setSelected(true); 360 saveViewState = true; 361 saveDataSourcesCbx.setSelected(true); 362 saveDataSources = true; 363 364 return GuiUtils.top( 365 GuiUtils.vbox( 366 Misc.newList( 367 GuiUtils.inset(new JLabel("Bundle save options:"), 368 new Insets(0, 5, 5, 0)), 369 saveJythonBox, 370 makeDataRelativeCbx))); 371 } 372 373 /** 374 * Have the user select an xidv filename and 375 * write the current application state to it. 376 * This also sets the current file name and 377 * adds the file to the history list. 378 */ 379 public void doSaveAs() { 380 String filename = 381 FileManager.getWriteFile(getArgsManager().getBundleFileFilters(), 382 "mcvz", getFileAccessory()); 383 if (filename == null) { 384 return; 385 } 386 setCurrentFileName(filename); 387 388 boolean prevMakeDataEditable = makeDataEditable; 389 makeDataEditable = makeDataEditableCbx.isSelected(); 390 391 boolean prevMakeDataRelative = makeDataRelative; 392 makeDataRelative = makeDataRelativeCbx.isSelected(); 393 if (doSave(filename)) { 394 getPublishManager().publishContent(filename, null, publishCbx); 395 getIdv().addToHistoryList(filename); 396 } 397 makeDataEditable = prevMakeDataEditable; 398 makeDataRelative = prevMakeDataRelative; 399 } 400 401 /** 402 * Overridden because McIDAS-V has a different definition of 403 * {@literal "default bundle"} than the IDV. 404 * 405 * <p>The McV default bundle is the {@literal "startup bundle"} from the 406 * advanced preferences.</p> 407 */ 408 @Override public void doOpenDefault() { 409 String bundle = StartupManager.getStartupPrefs().get("STARTUP_BUNDLE"); 410 // see javadoc for FileOption#parseFormat(String) 411 String path = FileOption.parseFormat(bundle)[1]; 412 if ((path == null) || path.isEmpty()) { 413 LogUtil.userMessage("No default bundle has been set."); 414 return; 415 } 416 File f = new File(path); 417 if (!f.exists()) { 418 LogUtil.userMessage("The default bundle: "+ f.getName() +" does not exist."); 419 return; 420 } 421 decodeXmlFile(path, true); 422 } 423 424 /** 425 * Prompt the user for a name and write out the given display control 426 * as a bundle into the user's {@code McIDAS-V/displaytemplates} directory. 427 * 428 * @param displayControl Display control to write. 429 * @param templateName Possibly {@code null} initial name for the template. 430 */ 431 @Override public void saveDisplayControlFavorite(DisplayControl displayControl, 432 String templateName) { 433 List cats = getCategories(BUNDLES_DISPLAY, Misc.newList(CAT_GENERAL)); 434 String fullFile = 435 getCategorizedFile("Save Display Template", templateName, 436 getBundles(BUNDLES_DISPLAY), 437 getBundleDirectory(BUNDLES_DISPLAY), cats, 438 getArgsManager().getXidvFileFilter().getPreferredSuffix(), false); 439 if (fullFile == null) { 440 return; 441 } 442 saveDisplayControl(displayControl, new File(fullFile)); 443 } 444 445 /** 446 * Overridden so that McIDAS-V can: 447 * <ul> 448 * <li>add better versioning information to bundles</li> 449 * <li>remove {@link edu.wisc.ssec.mcidasv.probes.ReadoutProbe ReadoutProbes} from the {@code displayControls} that are getting persisted.</li> 450 * <li>disallow saving multi-banded ADDE data sources until we have fix!</li> 451 * </ul> 452 */ 453 @Override protected boolean addToBundle(Hashtable data, List dataSources, 454 List displayControls, List viewManagers, 455 String jython) 456 { 457 logger.trace("hacking bundle output!"); 458 // add in some extra versioning information 459 StateManager stateManager = (StateManager)getIdv().getStateManager(); 460 if (data != null) { 461 data.put(ID_MCV_VERSION, stateManager.getVersionInfo()); 462 } 463 logger.trace("hacking displayControls={}", displayControls); 464 logger.trace("hacking dataSources={}", dataSources); 465 // remove ReadoutProbes from the list and possibly save off multibanded 466 // ADDE data sources 467 if (displayControls != null) { 468// Set<DataSourceImpl> observed = new HashSet<DataSourceImpl>(); 469 Map<DataSourceImpl, List<DataChoice>> observed = new LinkedHashMap<>(); 470 List<DisplayControl> newControls = new ArrayList<>(); 471 for (DisplayControl dc : (List<DisplayControl>)displayControls) { 472 if (dc instanceof ReadoutProbe) { 473 logger.trace("skipping readoutprobe!"); 474 continue; 475 } else if (dc instanceof ImagePlanViewControl) { 476 ImagePlanViewControl imageControl = (ImagePlanViewControl)dc; 477 List<DataSourceImpl> tmp = (List<DataSourceImpl>)imageControl.getDataSources(); 478 for (DataSourceImpl src : tmp) { 479 if (observed.containsKey(src)) { 480 observed.get(src).addAll(src.getDataChoices()); 481 logger.trace("already seen src={} new selection={}", src); 482 } else { 483 logger.trace("haven't seen src={}", src); 484 List<DataChoice> selected = new ArrayList<>(imageControl.getDataChoices()); 485 observed.put(src, selected); 486 } 487 } 488 logger.trace("found an image control: {} datasrcs={} datachoices={}", new Object[] { imageControl, imageControl.getDataSources(), imageControl.getDataChoices() }); 489 newControls.add(dc); 490 } else { 491 logger.trace("found some kinda thing: {}", dc.getClass().getName()); 492 newControls.add(dc); 493 } 494 } 495 for (Map.Entry<DataSourceImpl, List<DataChoice>> entry : observed.entrySet()) { 496 logger.trace("multibanded src={} choices={}", entry.getKey(), entry.getValue()); 497 } 498 displayControls = newControls; 499 } 500 501 return super.addToBundle(data, dataSources, displayControls, viewManagers, jython); 502 } 503 504 @Override public List getLocalBundles() { 505 List<SavedBundle> allBundles = new ArrayList<>(); 506 List<String> dirs = new ArrayList<>(); 507 String sitePath = getResourceManager().getSitePath(); 508 509 Collections.addAll(dirs, getStore().getLocalBundlesDir()); 510 511 if (sitePath != null) { 512 dirs.add(IOUtil.joinDir(sitePath, IdvObjectStore.DIR_BUNDLES)); 513 } 514 515 for (String top : dirs) { 516 List<File> subdirs = 517 IOUtil.getDirectories(Collections.singletonList(top), true); 518 for (File subdir : subdirs) { 519 loadBundlesInDirectory(allBundles, 520 fileToCategories(top, subdir.getPath()), subdir); 521 } 522 } 523 return allBundles; 524 } 525 526 protected void loadBundlesInDirectory(List<SavedBundle> allBundles, 527 List categories, File file) { 528 String[] localBundles = file.list(); 529 530 for (int i = 0; i < localBundles.length; i++) { 531 String filename = IOUtil.joinDir(file.toString(), localBundles[i]); 532 if (ArgumentManager.isBundle(filename)) { 533 allBundles.add(new SavedBundle(filename, 534 IOUtil.stripExtension(localBundles[i]), categories, true)); 535 } 536 } 537 } 538 539 /** 540 * <p> 541 * Overridden so that McIDAS-V can redirect to the version of this method 542 * that supports limiting the number of new windows. 543 * </p> 544 * 545 * @see #decodeXml(String, boolean, String, String, boolean, boolean, 546 * Hashtable, boolean, boolean, boolean) 547 */ 548 @Override public void decodeXml(String xml, final boolean fromCollab, 549 String xmlFile, final String label, final boolean showDialog, 550 final boolean shouldMerge, final Hashtable bundleProperties, 551 final boolean removeAll, final boolean letUserChangeData) 552 { 553 decodeXml(xml, fromCollab, xmlFile, label, showDialog, shouldMerge, 554 bundleProperties, removeAll, letUserChangeData, false); 555 } 556 557 /** 558 * <p> 559 * Hijacks control of the IDV's bundle loading facilities. Due to the way 560 * versions of McIDAS-V prior to alpha 10 handled tabs, the user will end 561 * up with a new window for each tab in the bundle. McIDAS-V alpha 10 has 562 * the ability to only create one new window and have everything else go 563 * into that window's tabs. 564 * </p> 565 * 566 * @see IdvPersistenceManager#decodeXmlFile(String, String, boolean, boolean, Hashtable) 567 * @see #decodeXml(String, boolean, String, String, boolean, boolean, Hashtable, 568 * boolean, boolean, boolean) 569 */ 570 @Override public boolean decodeXmlFile(String xmlFile, String label, 571 boolean checkToRemove, 572 boolean letUserChangeData, 573 Hashtable bundleProperties) { 574 575 logger.trace("loading bundle: '{}'", xmlFile); 576 if (xmlFile.isEmpty()) { 577 logger.warn("attempted to open a filename that is zero characters long"); 578 return false; 579 } 580 581 String name = label != null ? label : IOUtil.getFileTail(xmlFile); 582 583 boolean shouldMerge = getStore().get(PREF_OPEN_MERGE, true); 584 585 boolean removeAll = false; 586 587 boolean limitNewWindows = false; 588 589 boolean mergeLayers = false; 590 setMergeBundledLayers(false); 591 592 if (checkToRemove) { 593 // ok[0] = did the user press cancel 594 boolean[] ok = getPreferenceManager().getDoRemoveBeforeOpening(name); 595 596 if (!ok[0]) { 597 return false; 598 } 599 600 if (!ok[1] && !ok[2]) { // create new [opt=0] 601 removeAll = false; 602 shouldMerge = false; 603 mergeLayers = false; 604 } 605 if (!ok[1] && ok[2]) { // add new tabs [opt=2] 606 removeAll = false; 607 shouldMerge = true; 608 mergeLayers = false; 609 } 610 if (ok[1] && !ok[2]) { // merge with active [opt=1] 611 removeAll = false; 612 shouldMerge = false; 613 mergeLayers = true; 614 } 615 if (ok[1] && ok[2]) { // replace session [opt=3] 616 removeAll = true; 617 shouldMerge = true; 618 mergeLayers = false; 619 } 620 621 logger.trace("removeAll={} shouldMerge={} mergeLayers={}", new Object[] { removeAll, shouldMerge, mergeLayers }); 622 623 setMergeBundledLayers(mergeLayers); 624 625 if (removeAll) { 626 // Remove the displays first because, if we remove the data 627 // some state can get cleared that might be accessed from a 628 // timeChanged on the unremoved displays 629 getIdv().removeAllDisplays(); 630 // Then remove the data 631 getIdv().removeAllDataSources(); 632 } 633 634 if (ok.length == 4) { 635 limitNewWindows = ok[3]; 636 } 637 } 638 639 // the UI manager may need to know which ViewManager was active *before* 640 // we loaded the bundle. 641 lastBeforeBundle = getVMManager().getLastActiveViewManager(); 642 643 ArgumentManager argsManager = (ArgumentManager)getArgsManager(); 644 645 boolean isZidv = ArgumentManager.isZippedBundle(xmlFile); 646 647 if (!isZidv && !ArgumentManager.isXmlBundle(xmlFile)) { 648 //If we cannot tell what it is then try to open it as a zidv file 649 try { 650 ZipInputStream zin = 651 new ZipInputStream(IOUtil.getInputStream(xmlFile)); 652 isZidv = (zin.getNextEntry() != null); 653 } catch (Exception e) {} 654 } 655 656 String bundleContents = null; 657 try { 658 //Is this a zip file 659 logger.trace("bundle file={} isZipped={}", xmlFile, ArgumentManager.isZippedBundle(xmlFile)); 660 if (ArgumentManager.isZippedBundle(xmlFile)) { 661 boolean ask = getStore().get(PREF_ZIDV_ASK, true); 662 boolean toTmp = getStore().get(PREF_ZIDV_SAVETOTMP, true); 663 String dir = getStore().get(PREF_ZIDV_DIRECTORY, ""); 664 if (ask || ((dir.length() == 0) && !toTmp)) { 665 666 JCheckBox askCbx = 667 new JCheckBox("Don't show this again", !ask); 668 669 JRadioButton tmpBtn = 670 new JRadioButton("Write to temporary directory", toTmp); 671 672 JRadioButton dirBtn = 673 new JRadioButton("Write to:", !toTmp); 674 675 GuiUtils.buttonGroup(tmpBtn, dirBtn); 676 JTextField dirFld = new JTextField(dir, 30); 677 JComponent dirComp = GuiUtils.centerRight( 678 dirFld, 679 GuiUtils.makeFileBrowseButton( 680 dirFld, true, null)); 681 682 JComponent contents = 683 GuiUtils 684 .vbox(GuiUtils 685 .inset(new JLabel("Where should the data files be written to?"), 686 5), tmpBtn, 687 GuiUtils.hbox(dirBtn, dirComp), 688 GuiUtils 689 .inset(askCbx, 690 new Insets(5, 0, 0, 0))); 691 692 contents = GuiUtils.inset(contents, 5); 693 if (!GuiUtils.showOkCancelDialog(null, "Zip file data", 694 contents, null)) { 695 return false; 696 } 697 698 ask = !askCbx.isSelected(); 699 700 toTmp = tmpBtn.isSelected(); 701 702 dir = dirFld.getText().toString().trim(); 703 704 getStore().put(PREF_ZIDV_ASK, ask); 705 getStore().put(PREF_ZIDV_SAVETOTMP, toTmp); 706 getStore().put(PREF_ZIDV_DIRECTORY, dir); 707 getStore().save(); 708 } 709 710 String tmpDir = dir; 711 if (toTmp) { 712 tmpDir = getIdv().getObjectStore().getUserTmpDirectory(); 713 tmpDir = IOUtil.joinDir(tmpDir, Misc.getUniqueId()); 714 } 715 IOUtil.makeDir(tmpDir); 716 717 getStateManager().putProperty(PROP_ZIDVPATH, tmpDir); 718 ZipInputStream zin = 719 new ZipInputStream(IOUtil.getInputStream(xmlFile)); 720 ZipEntry ze = null; 721 722 while ((ze = zin.getNextEntry()) != null) { 723 String entryName = ze.getName(); 724 725 if (ArgumentManager.isXmlBundle(entryName.toLowerCase())) { 726 bundleContents = new String(IOUtil.readBytes(zin, 727 null, false)); 728 } else { 729// String xmlPath = IOUtil.joinDir(tmpDir, entryName); 730 if (IOUtil.writeTo(zin, new FileOutputStream(IOUtil.joinDir(tmpDir, entryName))) < 0L) { 731 return false; 732 } 733 } 734 } 735 } else { 736 Trace.call1("Decode.readContents"); 737 bundleContents = IOUtil.readContents(xmlFile); 738 Trace.call2("Decode.readContents"); 739 } 740 741 // TODO: this can probably go one day. I altered the prefix of the 742 // comp group classes. Old: "McIDASV...", new: "Mcv..." 743 // just gotta be sure to fix the references in the bundles. 744 // only people using the nightly build will be affected. 745 if (bundleContents != null) { 746 bundleContents = StringUtil.substitute(bundleContents, 747 OLD_COMP_STUFF, NEW_COMP_STUFF); 748 bundleContents = StringUtil.substitute(bundleContents, 749 OLD_SOURCE_MACRO, NEW_SOURCE_MACRO); 750 } 751 752 753 Trace.call1("Decode.decodeXml"); 754 decodeXml(bundleContents, false, xmlFile, name, true, 755 shouldMerge, bundleProperties, removeAll, 756 letUserChangeData, limitNewWindows); 757 Trace.call2("Decode.decodeXml"); 758 return true; 759 } catch (Throwable exc) { 760 if (contents == null) { 761 logException("Unable to load bundle:" + xmlFile, exc); 762 } else { 763 logException("Unable to evaluate bundle:" + xmlFile, exc); 764 } 765 return false; 766 } 767 } 768 769 // replace "old" references in a bundle's XML to the "new" classes. 770 private static final String OLD_COMP_STUFF = "McIDASVComp"; 771 private static final String NEW_COMP_STUFF = "McvComp"; 772 773 private static final String OLD_SOURCE_MACRO = "%fulldatasourcename%"; 774 private static final String NEW_SOURCE_MACRO = "%datasourcename%"; 775 776 /** 777 * <p>Overridden so that McIDAS-V can redirect to the version of this 778 * method that supports limiting the number of new windows.</p> 779 * 780 * @see #decodeXmlInner(String, boolean, String, String, boolean, boolean, Hashtable, boolean, boolean, boolean) 781 */ 782 @Override protected synchronized void decodeXmlInner(String xml, 783 boolean fromCollab, 784 String xmlFile, 785 String label, 786 boolean showDialog, 787 boolean shouldMerge, 788 Hashtable bundleProperties, 789 boolean didRemoveAll, 790 boolean changeData) { 791 792 decodeXmlInner(xml, fromCollab, xmlFile, label, showDialog, 793 shouldMerge, bundleProperties, didRemoveAll, changeData, 794 false); 795 796 } 797 798 /** 799 * <p> 800 * Overridden so that McIDAS-V can redirect to the version of this method 801 * that supports limiting the number of new windows. 802 * </p> 803 * 804 * @see #instantiateFromBundle(Hashtable, boolean, LoadBundleDialog, 805 * boolean, Hashtable, boolean, boolean, boolean) 806 */ 807 @Override protected void instantiateFromBundle(Hashtable ht, 808 boolean fromCollab, LoadBundleDialog loadDialog, boolean shouldMerge, 809 Hashtable bundleProperties, boolean didRemoveAll, 810 boolean letUserChangeData) throws Exception 811 { 812 instantiateFromBundle(ht, fromCollab, loadDialog, shouldMerge, 813 bundleProperties, didRemoveAll, letUserChangeData, false); 814 } 815 816 /** 817 * Hijacks the second part of the IDV bundle loading pipeline so that 818 * McIDAS-V can limit the number of new windows. 819 * 820 * @param xml XML within {@code xmlFile}. 821 * @param fromCollab Whether or not this bundle load was started by 822 * collaborator. 823 * @param xmlFile Bundled XML file. 824 * @param label Label to use in dialog title. 825 * @param showDialog Whether or not dialogs should be shown. 826 * @param shouldMerge Whether or not displays should be merged into 827 * existing displays. 828 * @param bundleProperties Mapping of bundle properties. 829 * @param removeAll Whether or not existing displays should be removed. 830 * @param letUserChangeData Whether or not users can alter data sources. 831 * @param limitWindows Whether or not multiple windows should be created. 832 * 833 * @see IdvPersistenceManager#decodeXml(String, boolean, 834 * String, String, boolean, boolean, Hashtable, boolean, boolean) 835 * @see #decodeXmlInner(String, boolean, String, String, boolean, boolean, 836 * Hashtable, boolean, boolean, boolean) 837 */ 838 public void decodeXml(final String xml, final boolean fromCollab, 839 final String xmlFile, final String label, final boolean showDialog, 840 final boolean shouldMerge, final Hashtable bundleProperties, 841 final boolean removeAll, final boolean letUserChangeData, 842 final boolean limitWindows) 843 { 844 845 if (!getStateManager().getShouldLoadBundlesSynchronously()) { 846 Runnable runnable = new Runnable() { 847 848 public void run() { 849 decodeXmlInner(xml, fromCollab, xmlFile, label, 850 showDialog, shouldMerge, bundleProperties, removeAll, 851 letUserChangeData, limitWindows); 852 } 853 }; 854 Misc.run(runnable); 855 } else { 856 decodeXmlInner(xml, fromCollab, xmlFile, label, showDialog, 857 shouldMerge, bundleProperties, removeAll, letUserChangeData, 858 limitWindows); 859 } 860 } 861 862 /** 863 * <p>Hijacks the third part of the bundle loading pipeline.</p> 864 * 865 * @param xml XML within {@code xmlFile}. 866 * @param fromCollab Whether or not this bundle load was started by 867 * collaborator. 868 * @param xmlFile Bundled XML file. 869 * @param label Label to use in dialog title. 870 * @param showDialog Whether or not dialogs should be shown. 871 * @param shouldMerge Whether or not displays should be merged into 872 * existing displays. 873 * @param bundleProperties Mapping of bundle properties. 874 * @param didRemoveAll Were existing displays removed? 875 * @param letUserChangeData Whether or not users can alter data sources. 876 * @param limitNewWindows Whether or not multiple windows should be created. 877 * 878 * @see IdvPersistenceManager#decodeXmlInner(String, boolean, String, String, boolean, boolean, Hashtable, boolean, boolean) 879 * @see #instantiateFromBundle(Hashtable, boolean, LoadBundleDialog, boolean, Hashtable, boolean, boolean, boolean) 880 */ 881 protected synchronized void decodeXmlInner(String xml, boolean fromCollab, 882 String xmlFile, String label, 883 boolean showDialog, 884 boolean shouldMerge, 885 Hashtable bundleProperties, 886 boolean didRemoveAll, 887 boolean letUserChangeData, 888 boolean limitNewWindows) { 889 890 LoadBundleDialog loadDialog = new LoadBundleDialog(this, label); 891 892 boolean inError = false; 893 894 if ( !fromCollab) { 895 showWaitCursor(); 896 if (showDialog) { 897 loadDialog.showDialog(); 898 } 899 } 900 901 if (xmlFile != null) { 902 getStateManager().putProperty(PROP_BUNDLEPATH, 903 IOUtil.getFileRoot(xmlFile)); 904 } 905 906 getStateManager().putProperty(PROP_LOADINGXML, true); 907 XmlEncoder xmlEncoder = null; 908 Hashtable<String, String> versions = null; 909 try { 910 xml = applyPropertiesToBundle(xml); 911 if (xml == null) { 912 return; 913 } 914 915// checkForBadMaps(xmlFile); 916 // perform any URL remapping that might be needed 917 ServerUrlRemapper remapper = new ServerUrlRemapper(getIdv()); 918 Element bundleRoot = remapper.remapUrlsInBundle(xml); 919 if (bundleRoot == null) { 920 return; 921 } 922 923 remapper = null; 924 925 xmlEncoder = getIdv().getEncoderForRead(); 926 Trace.call1("Decode.toObject"); 927 Object data = xmlEncoder.toObject(bundleRoot); 928 Trace.call2("Decode.toObject"); 929 930 if (data != null) { 931 Hashtable properties = new Hashtable(); 932 if (data instanceof Hashtable) { 933 Hashtable ht = (Hashtable) data; 934 935 versions = (Hashtable<String, String>)ht.get(ID_MCV_VERSION); 936 937 instantiateFromBundle(ht, fromCollab, loadDialog, 938 shouldMerge, bundleProperties, 939 didRemoveAll, letUserChangeData, 940 limitNewWindows); 941 942 } else if (data instanceof DisplayControl) { 943 ((DisplayControl) data).initAfterUnPersistence(getIdv(), 944 properties); 945 loadDialog.addDisplayControl((DisplayControl) data); 946 } else if (data instanceof DataSource) { 947 getIdv().getDataManager().addDataSource((DataSource)data); 948 } else if (data instanceof ColorTable) { 949 getColorTableManager().doImport(data, true); 950 } else { 951 LogUtil.userErrorMessage(log_, 952 "Decoding xml. Unknown object type:" 953 + data.getClass().getName()); 954 } 955 956 if ( !fromCollab && getIdv().haveCollabManager()) { 957 getCollabManager().write(getCollabManager().MSG_BUNDLE, 958 xml); 959 } 960 } 961 } catch (Throwable exc) { 962 if (xmlFile != null) { 963 logException("Error loading bundle: " + xmlFile, exc); 964 } else { 965 logException("Error loading bundle", exc); 966 } 967 968 inError = true; 969 } 970 971 if (!fromCollab) { 972 showNormalCursor(); 973 } 974 975 getStateManager().putProperty(PROP_BUNDLEPATH, ""); 976 getStateManager().putProperty(PROP_ZIDVPATH, ""); 977 getStateManager().putProperty(PROP_LOADINGXML, false); 978 979 boolean generatedExceptions = false; 980 if ((xmlEncoder != null) && (xmlEncoder.getExceptions() != null)) { 981 generatedExceptions = !xmlEncoder.getExceptions().isEmpty(); 982 } 983 984 if (generatedExceptions && getIdv().getInteractiveMode() && (versions != null)) { 985 String versionFromBundle = versions.get("mcv.version.general"); 986 if (versionFromBundle != null) { 987 String currentVersion = ((StateManager)getIdv().getStateManager()).getMcIdasVersion(); 988 int result = StateManager.compareVersions(currentVersion, versionFromBundle); 989 if (result > 0) { 990 // bundle from a amazing futuristic version of mcv 991 logger.warn("Bundle is from a newer version of McIDAS-V; please consider upgrading McIDAS-V to avoid any compatibility issues."); 992 } else if (result < 0) { 993 // bundle is from a stone age version of mcv 994 logger.warn("Bundle is from an older version of McIDAS-V"); 995 } else { 996 // bundle is from "this" version of mcv 997 } 998 } else { 999 // bundle may have been generated by the idv or a VERY old mcv. 1000 logger.warn("Bundle may have been generated by the IDV or a very early version of McIDAS-V."); 1001 } 1002 } 1003 xmlEncoder = null; 1004 1005 if (!inError && getIdv().getInteractiveMode() && (xmlFile != null)) { 1006 getIdv().addToHistoryList(xmlFile); 1007 } 1008 1009 loadDialog.dispose(); 1010 if (loadDialog.getShouldRemoveItems()) { 1011 List displayControls = loadDialog.getDisplayControls(); 1012 for (int i = 0; i < displayControls.size(); i++) { 1013 try { 1014 ((DisplayControl) displayControls.get(i)).doRemove(); 1015 } catch (Exception exc) { 1016 logger.warn("unexpected exception={}", exc); 1017 } 1018 } 1019 List dataSources = loadDialog.getDataSources(); 1020 for (int i = 0; i < dataSources.size(); i++) { 1021 getIdv().removeDataSource((DataSource) dataSources.get(i)); 1022 } 1023 } 1024 1025 loadDialog.clear(); 1026 } 1027 1028 // initial pass at trying to fix bundles with resources mcv hasn't heard of 1029 private void checkForBadMaps(final String bundlePath) { 1030 String xpath = "//property[@name=\"InitialMap\"]/string|//property[@name=\"MapStates\"]//property[@name=\"Source\"]/string"; 1031 for (Node node : XPathUtils.nodes(bundlePath, xpath)) { 1032 String mapPath = node.getTextContent(); 1033 if (mapPath.contains("_dir/")) { // hahaha this needs some work 1034 List<String> toks = StringUtil.split(mapPath, "_dir/"); 1035 if (toks.size() == 2) { 1036 String plugin = toks.get(0).replace("/", ""); 1037 logger.trace("plugin: {} map: {}", plugin, mapPath); 1038 } 1039 } else { 1040 logger.trace("normal map: {}", mapPath); 1041 } 1042 } 1043 } 1044 1045 /** 1046 * <p> 1047 * Builds a list of an incoming bundle's 1048 * {@link ucar.unidata.idv.ViewManager}s that are part of a component 1049 * group. 1050 * </p> 1051 * 1052 * <p> 1053 * The reason for only being interested in component groups is because any 1054 * windows <i>not</i> using component groups will be made into a dynamic 1055 * skin. The associated ViewManagers do not technically exist until the 1056 * skin has been "built", so there's nothing to do. These 1057 * ViewManagers must also be removed from the bundle's list of 1058 * ViewManagers. 1059 * </p> 1060 * 1061 * <p> 1062 * However, any ViewManagers associated with component groups still need to 1063 * appear in the bundle's ViewManager list, and that's where this method 1064 * comes into play! 1065 * </p> 1066 * 1067 * @param windows WindowInfos to be searched. 1068 * 1069 * @return List of ViewManagers inside any component groups. 1070 */ 1071 protected static List<ViewManager> extractCompGroupVMs( 1072 final List<WindowInfo> windows) 1073 { 1074 1075 List<ViewManager> newList = new ArrayList<ViewManager>(); 1076 1077 for (WindowInfo window : windows) { 1078 Collection<Object> comps = 1079 window.getPersistentComponents().values(); 1080 1081 for (Object comp : comps) { 1082 if (!(comp instanceof IdvComponentGroup)) { 1083 continue; 1084 } 1085 1086 IdvComponentGroup group = (IdvComponentGroup)comp; 1087 List<IdvComponentHolder> holders = 1088 group.getDisplayComponents(); 1089 1090 for (IdvComponentHolder holder : holders) { 1091 if (holder.getViewManagers() != null) { 1092 logger.trace("extracted: {}", holder.getViewManagers().size()); 1093 newList.addAll(holder.getViewManagers()); 1094 } 1095 } 1096 } 1097 } 1098 return newList; 1099 } 1100 1101 /** 1102 * <p>Does the work in fixing the collisions described in the 1103 * {@code instantiateFromBundle} javadoc. Basically just queries the 1104 * {@link ucar.unidata.idv.VMManager} for each 1105 * {@link ucar.unidata.idv.ViewManager}. If a match is found, a new ID is 1106 * generated and associated with the ViewManager, its 1107 * {@link ucar.unidata.idv.ViewDescriptor}, and any associated 1108 * {@link ucar.unidata.idv.DisplayControl}s.</p> 1109 * 1110 * @param vms ViewManagers in the incoming bundle. 1111 * 1112 * @see #instantiateFromBundle(Hashtable, boolean, LoadBundleDialog, boolean, Hashtable, boolean, boolean, boolean) 1113 */ 1114 protected void reverseCollisions(final List<ViewManager> vms) { 1115 for (ViewManager vm : vms) { 1116 ViewDescriptor vd = vm.getViewDescriptor(); 1117 ViewManager current = getVMManager().findViewManager(vd); 1118 if (current != null) { 1119 ViewDescriptor oldVd = current.getViewDescriptor(); 1120 String oldId = oldVd.getName(); 1121 String newId = "view_" + Misc.getUniqueId(); 1122 1123 oldVd.setName(newId); 1124 current.setUniqueId(newId); 1125 1126 List<DisplayControlImpl> controls = current.getControls(); 1127 for (DisplayControlImpl control : controls) { 1128 control.resetViewManager(oldId, newId); 1129 } 1130 } 1131 } 1132 } 1133 1134 /** 1135 * Builds a single window with a single component group. The group 1136 * contains component holders that correspond to each window or component 1137 * holder stored in the incoming bundle. 1138 * 1139 * @param windows The bundle's list of 1140 * {@link ucar.unidata.idv.ui.WindowInfo WindowInfos}. 1141 * 1142 * @return List of WindowInfos that contains only one element/window. 1143 * 1144 * @throws Exception Bubble up any exceptions from 1145 * {@code makeImpromptuSkin}. 1146 */ 1147 protected List<WindowInfo> injectComponentGroups( 1148 final List<WindowInfo> windows) throws Exception { 1149 1150 McvComponentGroup group = 1151 new McvComponentGroup(getIdv(), "Group"); 1152 1153 group.setLayout(McvComponentGroup.LAYOUT_TABS); 1154 1155 Hashtable<String, McvComponentGroup> persist = 1156 new Hashtable<>(); 1157 1158 for (WindowInfo window : windows) { 1159 List<IdvComponentHolder> holders = buildHolders(window); 1160 for (IdvComponentHolder holder : holders) 1161 group.addComponent(holder); 1162 } 1163 1164 persist.put("comp1", group); 1165 1166 // build a new window that contains our component group. 1167 WindowInfo limitedWindow = new WindowInfo(); 1168 limitedWindow.setPersistentComponents(persist); 1169 limitedWindow.setSkinPath(Constants.BLANK_COMP_GROUP); 1170 limitedWindow.setIsAMainWindow(true); 1171 limitedWindow.setTitle("Super Test"); 1172 limitedWindow.setViewManagers(new ArrayList<ViewManager>()); 1173 limitedWindow.setBounds(windows.get(0).getBounds()); 1174 1175 // make a new list so that we can populate the list of windows with 1176 // our single window. 1177 List<WindowInfo> newWindow = new ArrayList<>(); 1178 newWindow.add(limitedWindow); 1179 return newWindow; 1180 } 1181 1182 /** 1183 * Builds an altered copy of {@code windows} that preserves the 1184 * number of windows while ensuring all displays are inside component 1185 * holders. 1186 * 1187 * @param windows List of bundled windows. Cannot be {@code null}. 1188 * 1189 * @return {@code windows} with all displays inside component groups. 1190 * 1191 * @throws Exception Bubble up dynamic skin exceptions. 1192 * 1193 * @see #injectComponentGroups(List) 1194 */ 1195 // TODO: better name!! 1196 protected List<WindowInfo> betterInject(final List<WindowInfo> windows) 1197 throws Exception 1198 { 1199 1200 List<WindowInfo> newList = new ArrayList<>(); 1201 1202 for (WindowInfo window : windows) { 1203 McvComponentGroup group = new McvComponentGroup(getIdv(), "Group"); 1204 1205 group.setLayout(McvComponentGroup.LAYOUT_TABS); 1206 1207 Hashtable<String, McvComponentGroup> persist = 1208 new Hashtable<>(); 1209 1210 List<IdvComponentHolder> holders = buildHolders(window); 1211 for (IdvComponentHolder holder : holders) { 1212 group.addComponent(holder); 1213 } 1214 1215 persist.put("comp1", group); 1216 WindowInfo newWindow = new WindowInfo(); 1217 newWindow.setPersistentComponents(persist); 1218 newWindow.setSkinPath(Constants.BLANK_COMP_GROUP); 1219 newWindow.setIsAMainWindow(window.getIsAMainWindow()); 1220 newWindow.setViewManagers(new ArrayList<ViewManager>()); 1221 newWindow.setBounds(window.getBounds()); 1222 1223 newList.add(newWindow); 1224 } 1225 return newList; 1226 } 1227 1228 /** 1229 * Builds a list of component holders with all of {@code window}'s 1230 * displays. 1231 * 1232 * @param window Window containing displays. 1233 * 1234 * @return {@code List} of component holders for {@code window}. 1235 * 1236 * @throws Exception Bubble up any problems creating a dynamic skin. 1237 */ 1238 // TODO: refactor 1239 protected List<IdvComponentHolder> buildHolders(final WindowInfo window) 1240 throws Exception { 1241 1242 List<IdvComponentHolder> holders = 1243 new ArrayList<>(); 1244 1245 if (!window.getPersistentComponents().isEmpty()) { 1246 Collection<Object> comps = 1247 window.getPersistentComponents().values(); 1248 1249 for (Object comp : comps) { 1250 if (!(comp instanceof IdvComponentGroup)) { 1251 continue; 1252 } 1253 1254 IdvComponentGroup group = (IdvComponentGroup)comp; 1255 holders.addAll(McVGuiUtils.getComponentHolders(group)); 1256 } 1257 } else { 1258 holders.add(makeDynSkin(window)); 1259 } 1260 1261 return holders; 1262 } 1263 1264 /** 1265 * <p>Builds a list of any dynamic skins in the bundle and adds them to the 1266 * UIMananger's "cache" of encountered ViewManagers.</p> 1267 * 1268 * @param windows The bundle's windows. 1269 * 1270 * @return Any dynamic skins in {@code windows}. 1271 */ 1272 public List<ViewManager> mapDynamicSkins(final List<WindowInfo> windows) { 1273 List<ViewManager> vms = new ArrayList<ViewManager>(); 1274 for (WindowInfo window : windows) { 1275 Collection<Object> comps = 1276 window.getPersistentComponents().values(); 1277 1278 for (Object comp : comps) { 1279 if (!(comp instanceof IdvComponentGroup)) { 1280 continue; 1281 } 1282 1283 List<IdvComponentHolder> holders = 1284 new ArrayList<>( 1285 ((IdvComponentGroup)comp).getDisplayComponents()); 1286 1287 for (IdvComponentHolder holder : holders) { 1288 if (!McVGuiUtils.isDynamicSkin(holder)) { 1289 continue; 1290 } 1291 List<ViewManager> tmpvms = holder.getViewManagers(); 1292 for (ViewManager vm : tmpvms) { 1293 vms.add(vm); 1294 UIManager.savedViewManagers.put( 1295 vm.getViewDescriptor().getName(), vm); 1296 } 1297 holder.setViewManagers(new ArrayList<ViewManager>()); 1298 } 1299 } 1300 } 1301 return vms; 1302 } 1303 1304 /** 1305 * Attempts to reconcile McIDAS-V's ability to easily load all files in a 1306 * directory with the way the IDV expects file data sources to behave upon 1307 * unpersistence. 1308 * 1309 * <p>The problem is twofold: the paths referenced in the data source's 1310 * {@code Sources} may not exist, and the <i>persistence</i> code combines 1311 * each individual file into a blob. 1312 * 1313 * <p>The current solution is to note that the data source's 1314 * {@link PollingInfo} is used by 1315 * {@link ucar.unidata.data.FilesDataSource#initWithPollingInfo} to 1316 * replace the contents of the data source's file paths. Simply overwrite 1317 * {@code PollingInfo#filePaths} with the path to the blob. 1318 * 1319 * @param ds {@code List} of {@link DataSourceImpl DataSourceImpls} to 1320 * inspect and/or fix. Cannot be {@code null}. 1321 * @return true if able to load data ok 1322 * 1323 * @see #isBulkDataSource(DataSourceImpl) 1324 */ 1325 1326 private boolean fixBulkDataSources(final List<DataSourceImpl> ds) { 1327 String zidvPath = getStateManager().getProperty(PROP_ZIDVPATH, ""); 1328 1329 // bail out if the macro replacement cannot work 1330 // return true because this is ok, just no data bundled 1331 if (zidvPath.isEmpty()) { 1332 return true; 1333 } 1334 1335 for (DataSourceImpl d : ds) { 1336 boolean isBulk = isBulkDataSource(d); 1337 if (!isBulk) { 1338 continue; 1339 } 1340 1341 // err... now do the macro sub and replace the contents of 1342 // data paths with the singular element in temp paths? 1343 List<String> tempPaths = null; 1344 try { 1345 tempPaths = new ArrayList<>(d.getTmpPaths()); 1346 } catch (NullPointerException npe) { 1347 // one of the strides is not an integer, let user know 1348 String msg = "It appears there is some invalid data in this bundle.\n" + 1349 "Please ensure correctness of the original data sources.\n"; 1350 Object[] params = { msg }; 1351 JOptionPane.showMessageDialog(null, params, "Invalid Bundle", JOptionPane.OK_OPTION); 1352 return false; 1353 } 1354 String tempPath = tempPaths.get(0); 1355 tempPath = tempPath.replace(MACRO_ZIDVPATH, zidvPath); 1356 tempPaths.set(0, tempPath); 1357 PollingInfo p = d.getPollingInfo(); 1358 p.setFilePaths(tempPaths); 1359 } 1360 return true; 1361 } 1362 1363 /** 1364 * Attempts to determine whether or not a given {@link DataSourceImpl} is 1365 * the result of a McIDAS-V {@literal "bulk load"}. 1366 * 1367 * @param d {@code DataSourceImpl} to check. Cannot be {@code null}. 1368 * 1369 * @return {@code true} if the {@code DataSourceImpl} matched the criteria. 1370 */ 1371 private boolean isBulkDataSource(final DataSourceImpl d) { 1372 Hashtable properties = d.getProperties(); 1373 if (properties.containsKey("bulk.load")) { 1374 // woohoo! no need to do the guesswork. 1375 Object value = properties.get("bulk.load"); 1376 if (value instanceof String) { 1377 return Boolean.valueOf((String)value); 1378 } else if (value instanceof Boolean) { 1379 return (Boolean)value; 1380 } 1381 } 1382 1383 DataSourceDescriptor desc = d.getDescriptor(); 1384 boolean localFiles = desc.getFileSelection(); 1385 1386 List filePaths = d.getDataPaths(); 1387 List tempPaths = d.getTmpPaths(); 1388 if ((filePaths == null) || filePaths.isEmpty()) { 1389 return false; 1390 } 1391 1392 if ((tempPaths == null) || tempPaths.isEmpty()) { 1393 return false; 1394 } 1395 1396 // the least-involved heuristic i've found is: 1397 // localFiles == true 1398 // tempPaths.size() == 1 && filePaths.size() >= 2 1399 // and then we have a bulk load... 1400 // if those checks don't suffice, you can also look for the "prop.pollinfo" key 1401 // if the PollingInfo object has a filePaths list, with one element whose last directory matches 1402 // the data source "name" (then you are probably good). 1403 if (localFiles && (tempPaths.size() == 1) && (filePaths.size() >= 2)) { 1404 return true; 1405 } 1406 1407 // end of line 1408 return false; 1409 } 1410 1411 /** 1412 * Overridden so that McIDAS-V can preempt the IDV's bundle loading. 1413 * There will be problems if any of the incoming 1414 * {@link ViewManager ViewManagers} share an ID with an existing 1415 * ViewManager. While this case may seem unlikely, it can be triggered 1416 * when loading a bundle and then reloading. The problem is that the 1417 * ViewManagers are the same, and if the previous ViewManagers were not 1418 * removed, the IDV doesn't know what to do. 1419 * 1420 * <p>Assigning the incoming ViewManagers a new ID, <i>and associating its 1421 * {@link ViewDescriptor ViewDescriptors} and 1422 * {@link DisplayControl DisplayControls}</i> with the new ID fixes this 1423 * problem.</p> 1424 * 1425 * <p>McIDAS-V also allows the user to limit the number of new windows the 1426 * bundle may create. If enabled, one new window will be created, and any 1427 * additional windows will become tabs (component holders) inside the new 1428 * window.</p> 1429 * 1430 * <p>McIDAS-V also prefers the bundles being loaded to be in a 1431 * semi-regular regular state. For example, say you have bundle containing 1432 * only data. The bundle will probably not contain lists of WindowInfos or 1433 * ViewManagers. Perhaps the bundle contains nested component groups as 1434 * well! McIDAS-V will alter the unpersisted bundle state (<i>not the 1435 * actual bundle file</i>) to make it fit into the expected idiom. Mostly 1436 * this just entails wrapping things in component groups and holders while 1437 * "flattening" any nested component groups.</p> 1438 * 1439 * @param ht Holds unpersisted objects. 1440 * 1441 * @param fromCollab Did the bundle come from the collab stuff? 1442 * 1443 * @param loadDialog Show the bundle loading dialog? 1444 * 1445 * @param shouldMerge Merge bundle contents into an existing window? 1446 * 1447 * @param bundleProperties If non-null, use the set of time indices for 1448 * data sources? 1449 * 1450 * @param didRemoveAll Remove all data and displays? 1451 * 1452 * @param letUserChangeData Allow changes to the data path? 1453 * 1454 * @param limitNewWindows Only create one new window? 1455 * 1456 * @throws Exception if there was a problem re-instantiating the bundle. 1457 * 1458 * @see IdvPersistenceManager#instantiateFromBundle(Hashtable, boolean, LoadBundleDialog, boolean, Hashtable, boolean, boolean) 1459 */ 1460 // TODO: check the accuracy of the bundleProperties javadoc above 1461 protected void instantiateFromBundle(Hashtable ht, 1462 boolean fromCollab, 1463 LoadBundleDialog loadDialog, 1464 boolean shouldMerge, 1465 Hashtable bundleProperties, 1466 boolean didRemoveAll, 1467 boolean letUserChangeData, 1468 boolean limitNewWindows) 1469 throws Exception { 1470 1471 // hacky way of allowing other classes to determine whether or not 1472 // a bundle is loading 1473 bundleLoading = true; 1474 1475 // every bundle should have lists corresponding to these ids 1476 final String[] important = { 1477 ID_VIEWMANAGERS, ID_DISPLAYCONTROLS, ID_WINDOWS, 1478 }; 1479 populateEssentialLists(important, ht); 1480 1481 List<ViewManager> vms = (List)ht.get(ID_VIEWMANAGERS); 1482 List<DisplayControlImpl> controls = (List)ht.get(ID_DISPLAYCONTROLS); 1483 List<WindowInfo> windows = (List)ht.get(ID_WINDOWS); 1484 1485 List<DataSourceImpl> dataSources = (List)ht.get("datasources"); 1486 if (dataSources != null) { 1487 // TJJ - sometimes this will fail if underlying data is corrupt 1488 boolean ok = fixBulkDataSources(dataSources); 1489 if (! ok) { 1490 bundleLoading = false; 1491 return; 1492 } 1493 } 1494 1495 // older hydra bundles may contain ReadoutProbes in the list of 1496 // display controls. these are not needed, so they get removed. 1497// controls = removeReadoutProbes(controls); 1498 ht.put(ID_DISPLAYCONTROLS, controls); 1499 1500 if (vms.isEmpty() && windows.isEmpty() && !controls.isEmpty()) { 1501 List<ViewManager> fudged = generateViewManagers(controls); 1502 List<WindowInfo> buh = wrapViewManagers(fudged); 1503 1504 windows.addAll(buh); 1505 vms.addAll(fudged); 1506 } 1507 1508 // make sure that the list of windows contains no nested comp groups 1509 flattenWindows(windows); 1510 1511 // remove any component holders that don't contain displays 1512 windows = removeUIHolders(windows); 1513 1514 // generate new IDs for any collisions--typically happens if the same 1515 // bundle is loaded without removing the previously loaded VMs. 1516 reverseCollisions(vms); 1517 1518 // if the incoming bundle has dynamic skins, we've gotta be sure to 1519 // remove their ViewManagers from the bundle's list of ViewManagers! 1520 // remember, because they are dynamic skins, the ViewManagers should 1521 // not exist until the skin is built. 1522 if (McVGuiUtils.hasDynamicSkins(windows)) { 1523 mapDynamicSkins(windows); 1524 } 1525 1526 List<WindowInfo> newWindows; 1527 if (limitNewWindows && (windows.size() > 1)) { 1528 newWindows = injectComponentGroups(windows); 1529 } else { 1530 newWindows = betterInject(windows); 1531 } 1532 1533// if (limitNewWindows && windows.size() > 1) { 1534// // make a single new window with a single component group. 1535// // the group's holders will correspond to each window in the 1536// // bundle. 1537// List<WindowInfo> newWindows = injectComponentGroups(windows); 1538// ht.put(ID_WINDOWS, newWindows); 1539// 1540// // if there are any component groups in the bundle, we must 1541// // take care that their VMs appear in this list. VMs wrapped 1542// // in dynamic skins don't "exist" at this point, so they do 1543// // not need to be in this list. 1544// ht.put(ID_VIEWMANAGERS, extractCompGroupVMs(newWindows)); 1545// } 1546 1547 ht.put(ID_WINDOWS, newWindows); 1548 1549 ht.put(ID_VIEWMANAGERS, extractCompGroupVMs(newWindows)); 1550 1551 // hand our modified bundle information off to the IDV 1552 super.instantiateFromBundle(ht, fromCollab, loadDialog, shouldMerge, 1553 bundleProperties, didRemoveAll, 1554 letUserChangeData); 1555 1556 // no longer needed; the bundle is done loading. 1557 UIManager.savedViewManagers.clear(); 1558 bundleLoading = false; 1559 } 1560 1561// private List<DisplayControlImpl> removeReadoutProbes(final List<DisplayControlImpl> controls) { 1562// List<DisplayControlImpl> filtered = new ArrayList<DisplayControlImpl>(); 1563// for (DisplayControlImpl dc : controls) { 1564// if (dc instanceof ReadoutProbe) { 1565// try { 1566// dc.doRemove(); 1567// } catch (Exception e) { 1568// LogUtil.logException("Problem removing redundant readout probe", e); 1569// } 1570// } else if (dc != null) { 1571// filtered.add(dc); 1572// } 1573// } 1574// return filtered; 1575// } 1576 1577 private List<WindowInfo> wrapViewManagers(final List<ViewManager> vms) { 1578 List<WindowInfo> windows = new ArrayList<>(vms.size()); 1579 for (ViewManager vm : vms) { 1580 WindowInfo window = new WindowInfo(); 1581 window.setIsAMainWindow(true); 1582 window.setSkinPath("/ucar/unidata/idv/resources/skins/skin.xml"); 1583 window.setTitle("asdf"); 1584 List<ViewManager> vmList = new ArrayList<ViewManager>(); 1585 vmList.add(vm); 1586 window.setViewManagers(vmList); 1587 window.setBounds(new Rectangle(200, 200, 200, 200)); 1588 windows.add(window); 1589 } 1590 return windows; 1591 } 1592 1593 private List<ViewManager> generateViewManagers(final List<DisplayControlImpl> controls) { 1594 List<ViewManager> vms = new ArrayList<>(controls.size()); 1595 for (DisplayControlImpl control : controls) { 1596 ViewManager vm = getVMManager().findOrCreateViewManager(control.getDefaultViewDescriptor(), ""); 1597 vms.add(vm); 1598 } 1599 return vms; 1600 } 1601 1602 /** 1603 * Alters {@code windows} so that no windows in the bundle contain 1604 * nested component groups. 1605 * 1606 * @param windows {@code List} of windows to {@literal "flatten"}. 1607 */ 1608 protected void flattenWindows(final List<WindowInfo> windows) { 1609 for (WindowInfo window : windows) { 1610 Map<String, Object> persist = window.getPersistentComponents(); 1611 Set<Map.Entry<String, Object>> blah = persist.entrySet(); 1612 for (Map.Entry<String, Object> entry : blah) { 1613 if (!(entry.getValue() instanceof IdvComponentGroup)) { 1614 continue; 1615 } 1616 1617 IdvComponentGroup group = (IdvComponentGroup)entry.getValue(); 1618 if (McVGuiUtils.hasNestedGroups(group)) { 1619 entry.setValue(flattenGroup(group)); 1620 } 1621 } 1622 } 1623 } 1624 1625 /** 1626 * Alters {@code nested} so that there are no nested component groups. 1627 * 1628 * @param nested Component group to {@literal "flatten"}. 1629 * 1630 * @return An altered version of {@code nested} that contains no 1631 * nested component groups. 1632 */ 1633 protected IdvComponentGroup flattenGroup(final IdvComponentGroup nested) { 1634 IdvComponentGroup flat = 1635 new IdvComponentGroup(getIdv(), nested.getName()); 1636 1637 flat.setLayout(nested.getLayout()); 1638 flat.setShowHeader(nested.getShowHeader()); 1639 flat.setUniqueId(nested.getUniqueId()); 1640 1641 List<IdvComponentHolder> holders = 1642 McVGuiUtils.getComponentHolders(nested); 1643 1644 for (IdvComponentHolder holder : holders) { 1645 flat.addComponent(holder); 1646 holder.setParent(flat); 1647 } 1648 1649 return flat; 1650 } 1651 1652 /** 1653 * Remove component holders that are {@literal "UI-only"}. 1654 * 1655 * <p>{@literal "UI-only"} refers to things like having the dashboard 1656 * embedded in a component holder.</p> 1657 * 1658 * @param group Component group from which {@literal "UI-only"} holders will 1659 * be removed. 1660 * 1661 * @return An altered {@code group} containing only component holders 1662 * with displays. 1663 */ 1664 protected static List<IdvComponentHolder> removeUIHolders(final IdvComponentGroup group) { 1665 List<IdvComponentHolder> newHolders = 1666 new ArrayList<>(group.getDisplayComponents()); 1667 1668 for (IdvComponentHolder holder : newHolders) { 1669 if (McVGuiUtils.isUIHolder(holder)) { 1670 newHolders.remove(holder); 1671 } 1672 } 1673 1674 return newHolders; 1675 } 1676 1677 /** 1678 * Ensures that the lists corresponding to the ids in {@code ids} 1679 * actually exist in {@code table}, even if they are empty. 1680 * 1681 * @param ids IDs that should have a corresponding {@code List}. 1682 * @param table Table that should be a mapping of {@code ids} to 1683 * {@code Lists}. 1684 */ 1685 // TODO: not a fan of this method. 1686 protected static void populateEssentialLists(final String[] ids, final Hashtable<String, Object> table) { 1687 for (String id : ids) { 1688 if (table.get(id) == null) { 1689 table.put(id, new ArrayList<>()); 1690 } 1691 } 1692 } 1693 1694 /** 1695 * Returns an altered copy of {@code windows} containing only 1696 * component holders that have displays. 1697 * 1698 * <p>The IDV allows users to embed HTML controls or things like the 1699 * dashboard into component holders. This ability, while powerful, could 1700 * make for a confusing UI.</p> 1701 * 1702 * @param windows Windows from which {@literal "UI-only"} holders should be 1703 * removed. 1704 * 1705 * @return {@code List} of windows that contain displays. 1706 */ 1707 protected static List<WindowInfo> removeUIHolders( 1708 final List<WindowInfo> windows) { 1709 1710 List<WindowInfo> newList = new ArrayList<>(); 1711 for (WindowInfo window : windows) { 1712 // TODO: ought to write a WindowInfo cloning method 1713 WindowInfo newWin = new WindowInfo(); 1714 newWin.setViewManagers(window.getViewManagers()); 1715 newWin.setSkinPath(window.getSkinPath()); 1716 newWin.setIsAMainWindow(window.getIsAMainWindow()); 1717 newWin.setBounds(window.getBounds()); 1718 newWin.setTitle(window.getTitle()); 1719 1720 Hashtable<String, IdvComponentGroup> persist = 1721 new Hashtable<>(window.getPersistentComponents()); 1722 1723 for (Map.Entry<String, IdvComponentGroup> e : persist.entrySet()) { 1724 1725 IdvComponentGroup g = e.getValue(); 1726 1727 List<IdvComponentHolder> holders = g.getDisplayComponents(); 1728 if (holders == null || holders.isEmpty()) { 1729 continue; 1730 } 1731 1732 List<IdvComponentHolder> newHolders = new ArrayList<>(); 1733 1734 // filter out any holders that don't contain view managers 1735 for (IdvComponentHolder holder : holders) { 1736 if (!McVGuiUtils.isUIHolder(holder)) { 1737 newHolders.add(holder); 1738 } 1739 } 1740 1741 g.setDisplayComponents(newHolders); 1742 } 1743 1744 newWin.setPersistentComponents(persist); 1745 newList.add(newWin); 1746 } 1747 return newList; 1748 } 1749 1750 /** 1751 * Uses the {@link ViewManager ViewManagers} in {@code info} 1752 * to build a dynamic skin. 1753 * 1754 * @param info Window that needs to become a dynamic skin. 1755 * 1756 * @return {@link McvComponentHolder} containing the ViewManagers inside 1757 * {@code info}. 1758 * 1759 * @throws Exception Bubble up any XML problems. 1760 */ 1761 public McvComponentHolder makeDynSkin(final WindowInfo info) throws Exception { 1762 Document doc = XmlUtil.getDocument(SIMPLE_SKIN_TEMPLATE); 1763 Element root = doc.getDocumentElement(); 1764 1765 Element panel = XmlUtil.findElement(root, DYNSKIN_TAG_PANEL, 1766 DYNSKIN_ATTR_ID, DYNSKIN_ID_VALUE); 1767 1768 List<ViewManager> vms = info.getViewManagers(); 1769 1770 panel.setAttribute(DYNSKIN_ATTR_COLS, Integer.toString(vms.size())); 1771 1772 for (ViewManager vm : vms) { 1773 1774 Element view = doc.createElement(DYNSKIN_TAG_VIEW); 1775 1776 view.setAttribute(DYNSKIN_ATTR_CLASS, vm.getClass().getName()); 1777 view.setAttribute(DYNSKIN_ATTR_VIEWID, vm.getUniqueId()); 1778 1779 StringBuffer props = new StringBuffer(DYNSKIN_PROPS_GENERAL); 1780 1781 if (vm instanceof MapViewManager) { 1782 if (((MapViewManager)vm).getUseGlobeDisplay()) { 1783 props.append(DYNSKIN_PROPS_GLOBE); 1784 } 1785 } 1786 1787 view.setAttribute(DYNSKIN_ATTR_PROPS, props.toString()); 1788 1789 panel.appendChild(view); 1790 1791 UIManager.savedViewManagers.put(vm.getViewDescriptor().getName(), vm); 1792 } 1793 1794 McvComponentHolder holder = 1795 new McvComponentHolder(getIdv(), XmlUtil.toString(root)); 1796 1797 holder.setType(McvComponentHolder.TYPE_DYNAMIC_SKIN); 1798 holder.setName(DYNSKIN_TMPNAME); 1799 holder.doMakeContents(); 1800 return holder; 1801 } 1802 1803 public static IdvWindow buildDynamicSkin(int width, int height, int rows, int cols, boolean showWidgets, List<PyObject> panelTypes) throws Exception { 1804 String skinTemplate; 1805 if (showWidgets) { 1806 skinTemplate = SIMPLE_SKIN_TEMPLATE; 1807 } else { 1808 skinTemplate = BUILDWINDOW_SKIN_TEMPLATE; 1809 } 1810 Document doc = XmlUtil.getDocument(skinTemplate); 1811 Element root = doc.getDocumentElement(); 1812 Element panel = XmlUtil.findElement(root, DYNSKIN_TAG_PANEL, DYNSKIN_ATTR_ID, DYNSKIN_ID_VALUE); 1813 panel.setAttribute(DYNSKIN_ATTR_ROWS, Integer.toString(rows)); 1814 panel.setAttribute(DYNSKIN_ATTR_COLS, Integer.toString(cols)); 1815 Element view = doc.createElement(DYNSKIN_TAG_VIEW); 1816 for (PyObject panelType : panelTypes) { 1817 String panelTypeRepr = panelType.__repr__().toString(); 1818 Element node = doc.createElement(IdvUIManager.COMP_VIEW); 1819 StringBuilder props; 1820 if (showWidgets) { 1821 props = new StringBuilder(DYNSKIN_PROPS_GENERAL); 1822 } else { 1823 props = new StringBuilder(BUILDWINDOW_PROPS_GENERAL); 1824 } 1825 props.append("size=").append(width).append(':').append(height).append(';'); 1826 if ("MAP".equals(panelTypeRepr)) { 1827 node.setAttribute(IdvXmlUi.ATTR_CLASS, "ucar.unidata.idv.MapViewManager"); 1828 } else if ("GLOBE".equals(panelTypeRepr)) { 1829 node.setAttribute(IdvXmlUi.ATTR_CLASS, "ucar.unidata.idv.MapViewManager"); 1830 props.append(DYNSKIN_PROPS_GLOBE); 1831 } else if ("TRANSECT".equals(panelTypeRepr)) { 1832 node.setAttribute(IdvXmlUi.ATTR_CLASS, "ucar.unidata.idv.TransectViewManager"); 1833 } else if ("MAP2D".equals(panelTypeRepr)) { 1834 node.setAttribute(IdvXmlUi.ATTR_CLASS, "ucar.unidata.idv.MapViewManager"); 1835 props.append("use3D=false;"); 1836 } 1837 view.setAttribute(DYNSKIN_ATTR_PROPS, props.toString()); 1838 view.appendChild(node); 1839 } 1840 panel.appendChild(view); 1841 UIManager uiManager = (UIManager)McIDASV.getStaticMcv().getIdvUIManager(); 1842 String skinPath; 1843 if (showWidgets) { 1844 skinPath = Constants.BLANK_COMP_GROUP; 1845 } else { 1846 skinPath = BUILDWINDOW_COMP_GROUP_HIDE_WIDGETS; 1847 } 1848 Element skinRoot = XmlUtil.getRoot(skinPath, PersistenceManager.class); 1849 IdvWindow window = uiManager.createNewWindow(null, false, "McIDAS-V", skinPath, skinRoot, false, null); 1850 ComponentGroup group = window.getComponentGroups().get(0); 1851 McvComponentHolder holder = new McvComponentHolder(McIDASV.getStaticMcv(), XmlUtil.toString(root)); 1852 holder.setType(McvComponentHolder.TYPE_DYNAMIC_SKIN); 1853 group.addComponent(holder); 1854 return window; 1855 } 1856 1857 private static final String DYNSKIN_TMPNAME = "McIDAS-V buildWindow"; 1858 private static final String DYNSKIN_TAG_PANEL = "panel"; 1859 private static final String DYNSKIN_TAG_VIEW = "idv.view"; 1860 private static final String DYNSKIN_ATTR_ID = "id"; 1861 private static final String DYNSKIN_ATTR_COLS = "cols"; 1862 private static final String DYNSKIN_ATTR_ROWS = "rows"; 1863 private static final String DYNSKIN_ATTR_PROPS = "properties"; 1864 private static final String DYNSKIN_ATTR_CLASS = "class"; 1865 private static final String DYNSKIN_ATTR_VIEWID = "viewid"; 1866 private static final String DYNSKIN_PROPS_GLOBE = "useGlobeDisplay=true;initialMapResources=/edu/wisc/ssec/mcidasv/resources/maps.xml;"; 1867 private static final String DYNSKIN_PROPS_GENERAL = "clickToFocus=true;showToolBars=true;shareViews=true;showControlLegend=true;initialSplitPaneLocation=0.2;legendOnLeft=false;showEarthNavPanel=false;showControlLegend=false;shareGroup=view%versionuid%;"; 1868 private static final String DYNSKIN_ID_VALUE = "mcv.content"; 1869 1870 /** XML template for generating dynamic skins. */ 1871 private static final String SIMPLE_SKIN_TEMPLATE = 1872 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + 1873 "<skin embedded=\"true\">\n" + 1874 " <ui>\n" + 1875 " <panel layout=\"border\" bgcolor=\"red\">\n" + 1876 " <idv.menubar place=\"North\"/>\n" + 1877 " <panel layout=\"border\" place=\"Center\">\n" + 1878 " <panel layout=\"flow\" place=\"North\">\n" + 1879 " <idv.toolbar id=\"idv.toolbar\" place=\"West\"/>\n" + 1880 " <panel id=\"idv.favoritesbar\" place=\"North\"/>\n" + 1881 " </panel>\n" + 1882 " <panel embeddednode=\"true\" id=\"mcv.content\" layout=\"grid\" place=\"Center\">\n" + 1883 " </panel>" + 1884 " </panel>\n" + 1885 " <component idref=\"bottom_bar\"/>\n" + 1886 " </panel>\n" + 1887 " </ui>\n" + 1888 " <styles>\n" + 1889 " <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" + 1890 " <style class=\"textbtn\" space=\"2\" mouse_enter=\"ui.setText(idv.messagelabel,prop:tooltip)\" mouse_exit=\"ui.setText(idv.messagelabel,)\"/>\n" + 1891 " </styles>\n" + 1892 " <components>\n" + 1893 " <idv.statusbar place=\"South\" id=\"bottom_bar\"/>\n" + 1894 " </components>\n" + 1895 " <properties>\n" + 1896 " <property name=\"icon.wait.wait\" value=\"/ucar/unidata/idv/images/wait.gif\"/>\n" + 1897 " </properties>\n" + 1898 "</skin>\n"; 1899 1900 private static final String BUILDWINDOW_COMP_GROUP_HIDE_WIDGETS = 1901 "/edu/wisc/ssec/mcidasv/resources/skins/window/buildwindow-hidewidgets.xml"; 1902 1903 private static final String BUILDWINDOW_PROPS_GENERAL = "clickToFocus=true;showToolBars=false;TopBarVisible=false;shareViews=true;showControlLegend=true;initialSplitPaneLocation=0.2;legendOnLeft=false;showEarthNavPanel=false;showControlLegend=false;shareGroup=view%versionuid%;"; 1904 1905 /** Dynamic skin template for buildWindow. */ 1906 private static final String BUILDWINDOW_SKIN_TEMPLATE = 1907 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + 1908 "<skin embedded=\"true\">\n" + 1909 " <ui>\n" + 1910 " <panel layout=\"border\" bgcolor=\"red\">\n" + 1911 " <panel layout=\"border\" place=\"Center\">\n" + 1912 " <panel embeddednode=\"true\" id=\"mcv.content\" layout=\"grid\" place=\"Center\">\n" + 1913 " </panel>" + 1914 " </panel>\n" + 1915 " </panel>\n" + 1916 " </ui>\n" + 1917 " <properties>\n" + 1918 " <property name=\"icon.wait.wait\" value=\"/ucar/unidata/idv/images/wait.gif\"/>\n" + 1919 " </properties>\n" + 1920 "</skin>\n"; 1921 1922 /** 1923 * Write the parameter sets 1924 */ 1925 public void writeParameterSets() { 1926 if (parameterSets != null) { 1927 1928 //DAVEP: why is our write failing? 1929 if (!parameterSets.hasWritableResource()) { 1930 logger.trace("lost writable resource"); 1931 } 1932 1933 try { 1934 parameterSets.writeWritable(); 1935 } catch (IOException exc) { 1936 LogUtil.logException("Error writing " + parameterSets.getDescription(), exc); 1937 } 1938 1939 parameterSets.setWritableDocument(parameterSetsDocument, parameterSetsRoot); 1940 } 1941 } 1942 1943 /** 1944 * Get the node representing the parameterType 1945 * 1946 * @param parameterType What type of parameter set 1947 * 1948 * @return Element representing parameterType node 1949 */ 1950 private Element getParameterTypeNode(String parameterType) { 1951 if (parameterSets == null) { 1952 parameterSets = getIdv().getResourceManager().getXmlResources(ResourceManager.RSC_PARAMETERSETS); 1953 if (parameterSets.hasWritableResource()) { 1954 parameterSetsDocument = parameterSets.getWritableDocument("<parametersets></parametersets>"); 1955 parameterSetsRoot = parameterSets.getWritableRoot("<parametersets></parametersets>"); 1956 } else { 1957 logger.trace("no writable resource found"); 1958 return null; 1959 } 1960 } 1961 1962 Element parameterTypeNode = null; 1963 try { 1964 List<Element> rootTypes = XmlUtil.findChildren(parameterSetsRoot, parameterType); 1965 if (rootTypes.isEmpty()) { 1966 parameterTypeNode = parameterSetsDocument.createElement(parameterType); 1967 parameterSetsRoot.appendChild(parameterTypeNode); 1968 logger.trace("created new '{}' node", parameterType); 1969 writeParameterSets(); 1970 } 1971 else if (rootTypes.size() == 1) { 1972 parameterTypeNode = rootTypes.get(0); 1973 logger.trace("found existing '{}' node", parameterType); 1974 } 1975 } catch (Exception exc) { 1976 LogUtil.logException("Error loading " + parameterSets.getDescription(), exc); 1977 } 1978 return parameterTypeNode; 1979 } 1980 1981 /** 1982 * Get a list of all of the categories for the given parameterType 1983 * 1984 * @param parameterType What type of parameter set 1985 * 1986 * @return List of (String) categories 1987 */ 1988 public List<String> getAllParameterSetCategories(String parameterType) { 1989 List<String> allCategories = new ArrayList<>(); 1990 try { 1991 Element rootType = getParameterTypeNode(parameterType); 1992 if (rootType != null) { 1993 allCategories = 1994 XmlUtil.findDescendantNamesWithSeparator(rootType, TAG_FOLDER, CATEGORY_SEPARATOR); 1995 } 1996 } catch (Exception exc) { 1997 LogUtil.logException("Error loading " + parameterSets.getDescription(), exc); 1998 } 1999 return allCategories; 2000 } 2001 2002 2003 /** 2004 * Get the list of {@link ParameterSet}s that are writable 2005 * 2006 * @param parameterType The type of parameter set 2007 * 2008 * @return List of writable parameter sets 2009 */ 2010 public List<ParameterSet> getAllParameterSets(String parameterType) { 2011 List<ParameterSet> allParameterSets = new ArrayList<>(); 2012 try { 2013 Element rootType = getParameterTypeNode(parameterType); 2014 if (rootType != null) { 2015 List<String> defaults = 2016 XmlUtil.findDescendantNamesWithSeparator(rootType, TAG_DEFAULT, CATEGORY_SEPARATOR); 2017 2018 for (final String aDefault : defaults) { 2019 Element anElement = XmlUtil.getElementAtNamedPath(rootType, stringToCategories(aDefault)); 2020 List<String> defaultParts = stringToCategories(aDefault); 2021 int lastIndex = defaultParts.size() - 1; 2022 String defaultName = defaultParts.get(lastIndex); 2023 defaultParts.remove(lastIndex); 2024 String folderName = StringUtil.join(CATEGORY_SEPARATOR, defaultParts); 2025 ParameterSet newSet = new ParameterSet(defaultName, folderName, parameterType, anElement); 2026 allParameterSets.add(newSet); 2027 } 2028 } 2029 } catch (Exception exc) { 2030 LogUtil.logException("Error loading " + ResourceManager.RSC_PARAMETERSETS.getDescription(), exc); 2031 } 2032 return allParameterSets; 2033 } 2034 2035 /** 2036 * Add the directory. 2037 * 2038 * @param parameterType Type of parameter set. 2039 * @param category Category (really a {@literal ">"} delimited string). 2040 * 2041 * @return {@code true} if the create was successful. {@code false} if 2042 * there already is a category with that name 2043 */ 2044 public boolean addParameterSetCategory(String parameterType, String category) { 2045 logger.trace("parameter type: '{}' category: '{}'", parameterType, category); 2046 Element rootType = getParameterTypeNode(parameterType); 2047 XmlUtil.makeElementAtNamedPath(rootType, stringToCategories(category), TAG_FOLDER); 2048 writeParameterSets(); 2049 return true; 2050 } 2051 2052 /** 2053 * Delete the given parameter set 2054 * 2055 * @param parameterType The type of parameter set 2056 * @param set Parameter set to delete. 2057 */ 2058 public void deleteParameterSet(String parameterType, ParameterSet set) { 2059 Element parameterElement = set.getElement(); 2060 Node parentNode = parameterElement.getParentNode(); 2061 parentNode.removeChild((Node)parameterElement); 2062 writeParameterSets(); 2063 } 2064 2065 /** 2066 * Delete the directory and all of its contents that the given category 2067 * represents. 2068 * 2069 * @param parameterType Type of parameter set. 2070 * @param category Category (really a {@literal ">"} delimited string). 2071 */ 2072 public void deleteParameterSetCategory(String parameterType, String category) { 2073 Element rootType = getParameterTypeNode(parameterType); 2074 Element parameterSetElement = XmlUtil.getElementAtNamedPath(rootType, stringToCategories(category)); 2075 Node parentNode = parameterSetElement.getParentNode(); 2076 parentNode.removeChild((Node)parameterSetElement); 2077 writeParameterSets(); 2078 } 2079 2080 /** 2081 * Rename the parameter set. 2082 * 2083 * @param parameterType Type of parameter set. 2084 * @param set Parameter set. 2085 */ 2086 public void renameParameterSet(String parameterType, ParameterSet set) { 2087 String name = set.getName(); 2088 Element parameterElement = set.getElement(); 2089// while (true) { 2090 name = GuiUtils.getInput("Enter a new name", "Name: ", name); 2091 if (name == null) { 2092 return; 2093 } 2094 name = StringUtil.replaceList(name.trim(), 2095 new String[] { "<", ">", "/", "\\", "\"" }, 2096 new String[] { "_", "_", "_", "_", "_" } 2097 ); 2098 if (name.length() == 0) { 2099 return; 2100 } 2101// } 2102 parameterElement.setAttribute("name", name); 2103 writeParameterSets(); 2104 } 2105 2106 /** 2107 * Move the bundle to the given category area. 2108 * 2109 * @param parameterType Type of parameter set. 2110 * @param set Parameter set. 2111 * @param categories Where to move to. 2112 */ 2113 public void moveParameterSet(String parameterType, ParameterSet set, List categories) { 2114 Element rootType = getParameterTypeNode(parameterType); 2115 Element parameterElement = set.getElement(); 2116 Node parentNode = parameterElement.getParentNode(); 2117 parentNode.removeChild((Node)parameterElement); 2118 Node newParentNode = XmlUtil.getElementAtNamedPath(rootType, categories); 2119 newParentNode.appendChild(parameterElement); 2120 writeParameterSets(); 2121 } 2122 2123 /** 2124 * Move the bundle category. 2125 * 2126 * @param parameterType Type of parameter set. 2127 * @param fromCategories Category to move. 2128 * @param toCategories Where to move to. 2129 */ 2130 public void moveParameterSetCategory(String parameterType, List fromCategories, List toCategories) { 2131 Element rootType = getParameterTypeNode(parameterType); 2132 Element parameterSetElementFrom = XmlUtil.getElementAtNamedPath(rootType, fromCategories); 2133 Node parentNode = parameterSetElementFrom.getParentNode(); 2134 parentNode.removeChild((Node)parameterSetElementFrom); 2135 Node parentNodeTo = (Node)XmlUtil.getElementAtNamedPath(rootType, toCategories); 2136 parentNodeTo.appendChild(parameterSetElementFrom); 2137 writeParameterSets(); 2138 } 2139 2140 /** 2141 * Show the Save Parameter Set dialog. 2142 * 2143 * @param parameterType Type of parameter set. 2144 * @param parameterValues Values to save. 2145 * 2146 * @return Whether or not the parameter set was saved. 2147 */ 2148 public boolean saveParameterSet(String parameterType, Hashtable parameterValues) { 2149 try { 2150 String title = "Save Parameter Set"; 2151 2152 // Create the category dropdown 2153 List<String> categories = getAllParameterSetCategories(parameterType); 2154 final JComboBox catBox = new JComboBox(); 2155 catBox.setToolTipText( 2156 "<html>Categories can be entered manually. <br>Use '>' as the category delimiter. e.g.:<br>General > Subcategory</html>"); 2157 catBox.setEditable(true); 2158 McVGuiUtils.setComponentWidth(catBox, McVGuiUtils.ELEMENT_DOUBLE_WIDTH); 2159 GuiUtils.setListData(catBox, categories); 2160 2161 // Create the default name dropdown 2162 final JComboBox nameBox = new JComboBox(); 2163 nameBox.setEditable(true); 2164 2165 List<ParameterSet> pSets = getAllParameterSets(parameterType); 2166 List tails = new ArrayList(pSets.size() * 2); 2167 for (int i = 0; i < pSets.size(); i++) { 2168 ParameterSet pSet = pSets.get(i); 2169 tails.add(new TwoFacedObject(pSet.getName(), pSet)); 2170 } 2171 java.util.Collections.sort(tails); 2172 2173 tails.add(0, new TwoFacedObject("", null)); 2174 GuiUtils.setListData(nameBox, tails); 2175 nameBox.addActionListener(new ActionListener() { 2176 public void actionPerformed(ActionEvent ae) { 2177 Object selected = nameBox.getSelectedItem(); 2178 if ( !(selected instanceof TwoFacedObject)) { 2179 return; 2180 } 2181 TwoFacedObject tfo = (TwoFacedObject) selected; 2182 List cats = ((ParameterSet) tfo.getId()).getCategories(); 2183 // if ((cats.size() > 0) && !catSelected) { 2184 if (!cats.isEmpty()) { 2185 catBox.setSelectedItem( 2186 StringUtil.join(CATEGORY_SEPARATOR, cats)); 2187 } 2188 } 2189 }); 2190 2191 JPanel panel = McVGuiUtils.sideBySide( 2192 McVGuiUtils.makeLabeledComponent("Category:", catBox), 2193 McVGuiUtils.makeLabeledComponent("Name:", nameBox) 2194 ); 2195 2196 String name = ""; 2197 String category = ""; 2198 while (true) { 2199 if ( !GuiUtils.askOkCancel(title, panel)) { 2200 return false; 2201 } 2202 name = StringUtil.replaceList(nameBox.getSelectedItem().toString().trim(), 2203 new String[] { "<", ">", "/", "\\", "\"" }, 2204 new String[] { "_", "_", "_", "_", "_" } 2205 ); 2206 if (name.isEmpty()) { 2207 LogUtil.userMessage("Please enter a name"); 2208 continue; 2209 } 2210 category = StringUtil.replaceList(catBox.getSelectedItem().toString().trim(), 2211 new String[] { "/", "\\", "\"" }, 2212 new String[] { "_", "_", "_" } 2213 ); 2214 if (category.isEmpty()) { 2215 LogUtil.userMessage("Please enter a category"); 2216 continue; 2217 } 2218 break; 2219 } 2220 2221 // Create a new element from the hashtable 2222 Element rootType = getParameterTypeNode(parameterType); 2223 Element parameterElement = parameterSetsDocument.createElement(TAG_DEFAULT); 2224 for (Enumeration e = parameterValues.keys(); e.hasMoreElements(); ) { 2225 Object nextKey = e.nextElement(); 2226 String attribute = (String)nextKey; 2227 String value = (String)parameterValues.get(nextKey); 2228 parameterElement.setAttribute(attribute, value); 2229 } 2230 2231 // Set the name to the one we entered 2232 parameterElement.setAttribute(ATTR_NAME, name); 2233 2234 Element categoryNode = XmlUtil.makeElementAtNamedPath(rootType, stringToCategories(category), TAG_FOLDER); 2235// Element categoryNode = XmlUtil.getElementAtNamedPath(rootType, stringToCategories(category)); 2236 2237 categoryNode.appendChild(parameterElement); 2238 writeParameterSets(); 2239 } 2240 catch (Exception e) { 2241 logger.error("error while saving parameter set", e); 2242 return false; 2243 } 2244 2245 return true; 2246 } 2247 2248}