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