001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2016 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 {@link ucar.unidata.data.FilesDataSource#initWithPollingInfo} 1289 * to replace the contents of the data source's file paths. Simply 1290 * overwrite {@code PollingInfo#filePaths} with the path to the blob. 1291 * 1292 * @param ds {@code List} of {@link DataSourceImpl}s to inspect and/or fix. 1293 * Cannot be {@code null}. 1294 * 1295 * @see #isBulkDataSource(DataSourceImpl) 1296 */ 1297 private void fixBulkDataSources(final List<DataSourceImpl> ds) { 1298 String zidvPath = getStateManager().getProperty(PROP_ZIDVPATH, ""); 1299 1300 // bail out if the macro replacement cannot work 1301 if (zidvPath.isEmpty()) { 1302 return; 1303 } 1304 1305 for (DataSourceImpl d : ds) { 1306 boolean isBulk = isBulkDataSource(d); 1307 if (!isBulk) { 1308 continue; 1309 } 1310 1311 // err... now do the macro sub and replace the contents of 1312 // data paths with the singular element in temp paths? 1313 List<String> tempPaths = new ArrayList<>(d.getTmpPaths()); 1314 String tempPath = tempPaths.get(0); 1315 tempPath = tempPath.replace(MACRO_ZIDVPATH, zidvPath); 1316 tempPaths.set(0, tempPath); 1317 PollingInfo p = d.getPollingInfo(); 1318 p.setFilePaths(tempPaths); 1319 } 1320 } 1321 1322 /** 1323 * Attempts to determine whether or not a given {@link DataSourceImpl} is 1324 * the result of a McIDAS-V {@literal "bulk load"}. 1325 * 1326 * @param d {@code DataSourceImpl} to check. Cannot be {@code null}. 1327 * 1328 * @return {@code true} if the {@code DataSourceImpl} matched the criteria. 1329 */ 1330 private boolean isBulkDataSource(final DataSourceImpl d) { 1331 Hashtable properties = d.getProperties(); 1332 if (properties.containsKey("bulk.load")) { 1333 // woohoo! no need to do the guesswork. 1334 Object value = properties.get("bulk.load"); 1335 if (value instanceof String) { 1336 return Boolean.valueOf((String)value); 1337 } else if (value instanceof Boolean) { 1338 return (Boolean)value; 1339 } 1340 } 1341 1342 DataSourceDescriptor desc = d.getDescriptor(); 1343 boolean localFiles = desc.getFileSelection(); 1344 1345 List filePaths = d.getDataPaths(); 1346 List tempPaths = d.getTmpPaths(); 1347 if ((filePaths == null) || filePaths.isEmpty()) { 1348 return false; 1349 } 1350 1351 if ((tempPaths == null) || tempPaths.isEmpty()) { 1352 return false; 1353 } 1354 1355 // the least-involved heuristic i've found is: 1356 // localFiles == true 1357 // tempPaths.size() == 1 && filePaths.size() >= 2 1358 // and then we have a bulk load... 1359 // if those checks don't suffice, you can also look for the "prop.pollinfo" key 1360 // if the PollingInfo object has a filePaths list, with one element whose last directory matches 1361 // the data source "name" (then you are probably good). 1362 if (localFiles && (tempPaths.size() == 1) && (filePaths.size() >= 2)) { 1363 return true; 1364 } 1365 1366 // end of line 1367 return false; 1368 } 1369 1370 /** 1371 * Overridden so that McIDAS-V can preempt the IDV's bundle loading. 1372 * There will be problems if any of the incoming 1373 * {@link ViewManager ViewManagers} share an ID with an existing 1374 * ViewManager. While this case may seem unlikely, it can be triggered 1375 * when loading a bundle and then reloading. The problem is that the 1376 * ViewManagers are the same, and if the previous ViewManagers were not 1377 * removed, the IDV doesn't know what to do. 1378 * 1379 * <p>Assigning the incoming ViewManagers a new ID, <i>and associating its 1380 * {@link ViewDescriptor ViewDescriptors} and 1381 * {@link DisplayControl DisplayControls}</i> with the new ID fixes this 1382 * problem.</p> 1383 * 1384 * <p>McIDAS-V also allows the user to limit the number of new windows the 1385 * bundle may create. If enabled, one new window will be created, and any 1386 * additional windows will become tabs (component holders) inside the new 1387 * window.</p> 1388 * 1389 * <p>McIDAS-V also prefers the bundles being loaded to be in a 1390 * semi-regular regular state. For example, say you have bundle containing 1391 * only data. The bundle will probably not contain lists of WindowInfos or 1392 * ViewManagers. Perhaps the bundle contains nested component groups as 1393 * well! McIDAS-V will alter the unpersisted bundle state (<i>not the 1394 * actual bundle file</i>) to make it fit into the expected idiom. Mostly 1395 * this just entails wrapping things in component groups and holders while 1396 * "flattening" any nested component groups.</p> 1397 * 1398 * @param ht Holds unpersisted objects. 1399 * 1400 * @param fromCollab Did the bundle come from the collab stuff? 1401 * 1402 * @param loadDialog Show the bundle loading dialog? 1403 * 1404 * @param shouldMerge Merge bundle contents into an existing window? 1405 * 1406 * @param bundleProperties If non-null, use the set of time indices for 1407 * data sources? 1408 * 1409 * @param didRemoveAll Remove all data and displays? 1410 * 1411 * @param letUserChangeData Allow changes to the data path? 1412 * 1413 * @param limitNewWindows Only create one new window? 1414 * 1415 * @throws Exception if there was a problem re-instantiating the bundle. 1416 * 1417 * @see IdvPersistenceManager#instantiateFromBundle(Hashtable, boolean, LoadBundleDialog, boolean, Hashtable, boolean, boolean) 1418 */ 1419 // TODO: check the accuracy of the bundleProperties javadoc above 1420 protected void instantiateFromBundle(Hashtable ht, 1421 boolean fromCollab, 1422 LoadBundleDialog loadDialog, 1423 boolean shouldMerge, 1424 Hashtable bundleProperties, 1425 boolean didRemoveAll, 1426 boolean letUserChangeData, 1427 boolean limitNewWindows) 1428 throws Exception { 1429 1430 // hacky way of allowing other classes to determine whether or not 1431 // a bundle is loading 1432 bundleLoading = true; 1433 1434 // every bundle should have lists corresponding to these ids 1435 final String[] important = { 1436 ID_VIEWMANAGERS, ID_DISPLAYCONTROLS, ID_WINDOWS, 1437 }; 1438 populateEssentialLists(important, ht); 1439 1440 List<ViewManager> vms = (List)ht.get(ID_VIEWMANAGERS); 1441 List<DisplayControlImpl> controls = (List)ht.get(ID_DISPLAYCONTROLS); 1442 List<WindowInfo> windows = (List)ht.get(ID_WINDOWS); 1443 1444 List<DataSourceImpl> dataSources = (List)ht.get("datasources"); 1445 if (dataSources != null) { 1446 fixBulkDataSources(dataSources); 1447 } 1448 1449 // older hydra bundles may contain ReadoutProbes in the list of 1450 // display controls. these are not needed, so they get removed. 1451// controls = removeReadoutProbes(controls); 1452 ht.put(ID_DISPLAYCONTROLS, controls); 1453 1454 if (vms.isEmpty() && windows.isEmpty() && !controls.isEmpty()) { 1455 List<ViewManager> fudged = generateViewManagers(controls); 1456 List<WindowInfo> buh = wrapViewManagers(fudged); 1457 1458 windows.addAll(buh); 1459 vms.addAll(fudged); 1460 } 1461 1462 // make sure that the list of windows contains no nested comp groups 1463 flattenWindows(windows); 1464 1465 // remove any component holders that don't contain displays 1466 windows = removeUIHolders(windows); 1467 1468 // generate new IDs for any collisions--typically happens if the same 1469 // bundle is loaded without removing the previously loaded VMs. 1470 reverseCollisions(vms); 1471 1472 // if the incoming bundle has dynamic skins, we've gotta be sure to 1473 // remove their ViewManagers from the bundle's list of ViewManagers! 1474 // remember, because they are dynamic skins, the ViewManagers should 1475 // not exist until the skin is built. 1476 if (McVGuiUtils.hasDynamicSkins(windows)) { 1477 mapDynamicSkins(windows); 1478 } 1479 1480 List<WindowInfo> newWindows; 1481 if (limitNewWindows && (windows.size() > 1)) { 1482 newWindows = injectComponentGroups(windows); 1483 } else { 1484 newWindows = betterInject(windows); 1485 } 1486 1487// if (limitNewWindows && windows.size() > 1) { 1488// // make a single new window with a single component group. 1489// // the group's holders will correspond to each window in the 1490// // bundle. 1491// List<WindowInfo> newWindows = injectComponentGroups(windows); 1492// ht.put(ID_WINDOWS, newWindows); 1493// 1494// // if there are any component groups in the bundle, we must 1495// // take care that their VMs appear in this list. VMs wrapped 1496// // in dynamic skins don't "exist" at this point, so they do 1497// // not need to be in this list. 1498// ht.put(ID_VIEWMANAGERS, extractCompGroupVMs(newWindows)); 1499// } 1500 1501 ht.put(ID_WINDOWS, newWindows); 1502 1503 ht.put(ID_VIEWMANAGERS, extractCompGroupVMs(newWindows)); 1504 1505 // hand our modified bundle information off to the IDV 1506 super.instantiateFromBundle(ht, fromCollab, loadDialog, shouldMerge, 1507 bundleProperties, didRemoveAll, 1508 letUserChangeData); 1509 1510 // no longer needed; the bundle is done loading. 1511 UIManager.savedViewManagers.clear(); 1512 bundleLoading = false; 1513 } 1514 1515// private List<DisplayControlImpl> removeReadoutProbes(final List<DisplayControlImpl> controls) { 1516// List<DisplayControlImpl> filtered = new ArrayList<DisplayControlImpl>(); 1517// for (DisplayControlImpl dc : controls) { 1518// if (dc instanceof ReadoutProbe) { 1519// try { 1520// dc.doRemove(); 1521// } catch (Exception e) { 1522// LogUtil.logException("Problem removing redundant readout probe", e); 1523// } 1524// } else if (dc != null) { 1525// filtered.add(dc); 1526// } 1527// } 1528// return filtered; 1529// } 1530 1531 private List<WindowInfo> wrapViewManagers(final List<ViewManager> vms) { 1532 List<WindowInfo> windows = new ArrayList<>(vms.size()); 1533 for (ViewManager vm : vms) { 1534 WindowInfo window = new WindowInfo(); 1535 window.setIsAMainWindow(true); 1536 window.setSkinPath("/ucar/unidata/idv/resources/skins/skin.xml"); 1537 window.setTitle("asdf"); 1538 List<ViewManager> vmList = new ArrayList<ViewManager>(); 1539 vmList.add(vm); 1540 window.setViewManagers(vmList); 1541 window.setBounds(new Rectangle(200, 200, 200, 200)); 1542 windows.add(window); 1543 } 1544 return windows; 1545 } 1546 1547 private List<ViewManager> generateViewManagers(final List<DisplayControlImpl> controls) { 1548 List<ViewManager> vms = new ArrayList<>(controls.size()); 1549 for (DisplayControlImpl control : controls) { 1550 ViewManager vm = getVMManager().findOrCreateViewManager(control.getDefaultViewDescriptor(), ""); 1551 vms.add(vm); 1552 } 1553 return vms; 1554 } 1555 1556 /** 1557 * Alters {@code windows} so that no windows in the bundle contain 1558 * nested component groups. 1559 * 1560 * @param windows {@code List} of windows to {@literal "flatten"}. 1561 */ 1562 protected void flattenWindows(final List<WindowInfo> windows) { 1563 for (WindowInfo window : windows) { 1564 Map<String, Object> persist = window.getPersistentComponents(); 1565 Set<Map.Entry<String, Object>> blah = persist.entrySet(); 1566 for (Map.Entry<String, Object> entry : blah) { 1567 if (!(entry.getValue() instanceof IdvComponentGroup)) { 1568 continue; 1569 } 1570 1571 IdvComponentGroup group = (IdvComponentGroup)entry.getValue(); 1572 if (McVGuiUtils.hasNestedGroups(group)) { 1573 entry.setValue(flattenGroup(group)); 1574 } 1575 } 1576 } 1577 } 1578 1579 /** 1580 * Alters {@code nested} so that there are no nested component groups. 1581 * 1582 * @param nested Component group to {@literal "flatten"}. 1583 * 1584 * @return An altered version of {@code nested} that contains no 1585 * nested component groups. 1586 */ 1587 protected IdvComponentGroup flattenGroup(final IdvComponentGroup nested) { 1588 IdvComponentGroup flat = 1589 new IdvComponentGroup(getIdv(), nested.getName()); 1590 1591 flat.setLayout(nested.getLayout()); 1592 flat.setShowHeader(nested.getShowHeader()); 1593 flat.setUniqueId(nested.getUniqueId()); 1594 1595 List<IdvComponentHolder> holders = 1596 McVGuiUtils.getComponentHolders(nested); 1597 1598 for (IdvComponentHolder holder : holders) { 1599 flat.addComponent(holder); 1600 holder.setParent(flat); 1601 } 1602 1603 return flat; 1604 } 1605 1606 /** 1607 * Remove component holders that are {@literal "UI-only"}. 1608 * 1609 * <p>{@literal "UI-only"} refers to things like having the dashboard 1610 * embedded in a component holder.</p> 1611 * 1612 * @param group Component group from which {@literal "UI-only"} holders will 1613 * be removed. 1614 * 1615 * @return An altered {@code group} containing only component holders 1616 * with displays. 1617 */ 1618 protected static List<IdvComponentHolder> removeUIHolders(final IdvComponentGroup group) { 1619 List<IdvComponentHolder> newHolders = 1620 new ArrayList<>(group.getDisplayComponents()); 1621 1622 for (IdvComponentHolder holder : newHolders) { 1623 if (McVGuiUtils.isUIHolder(holder)) { 1624 newHolders.remove(holder); 1625 } 1626 } 1627 1628 return newHolders; 1629 } 1630 1631 /** 1632 * Ensures that the lists corresponding to the ids in {@code ids} 1633 * actually exist in {@code table}, even if they are empty. 1634 * 1635 * @param ids IDs that should have a corresponding {@code List}. 1636 * @param table Table that should be a mapping of {@code ids} to 1637 * {@code Lists}. 1638 */ 1639 // TODO: not a fan of this method. 1640 protected static void populateEssentialLists(final String[] ids, final Hashtable<String, Object> table) { 1641 for (String id : ids) { 1642 if (table.get(id) == null) { 1643 table.put(id, new ArrayList<>()); 1644 } 1645 } 1646 } 1647 1648 /** 1649 * Returns an altered copy of {@code windows} containing only 1650 * component holders that have displays. 1651 * 1652 * <p>The IDV allows users to embed HTML controls or things like the 1653 * dashboard into component holders. This ability, while powerful, could 1654 * make for a confusing UI.</p> 1655 * 1656 * @param windows Windows from which {@literal "UI-only"} holders should be 1657 * removed. 1658 * 1659 * @return {@code List} of windows that contain displays. 1660 */ 1661 protected static List<WindowInfo> removeUIHolders( 1662 final List<WindowInfo> windows) { 1663 1664 List<WindowInfo> newList = new ArrayList<>(); 1665 for (WindowInfo window : windows) { 1666 // TODO: ought to write a WindowInfo cloning method 1667 WindowInfo newWin = new WindowInfo(); 1668 newWin.setViewManagers(window.getViewManagers()); 1669 newWin.setSkinPath(window.getSkinPath()); 1670 newWin.setIsAMainWindow(window.getIsAMainWindow()); 1671 newWin.setBounds(window.getBounds()); 1672 newWin.setTitle(window.getTitle()); 1673 1674 Hashtable<String, IdvComponentGroup> persist = 1675 new Hashtable<>(window.getPersistentComponents()); 1676 1677 for (Map.Entry<String, IdvComponentGroup> e : persist.entrySet()) { 1678 1679 IdvComponentGroup g = e.getValue(); 1680 1681 List<IdvComponentHolder> holders = g.getDisplayComponents(); 1682 if (holders == null || holders.isEmpty()) { 1683 continue; 1684 } 1685 1686 List<IdvComponentHolder> newHolders = new ArrayList<>(); 1687 1688 // filter out any holders that don't contain view managers 1689 for (IdvComponentHolder holder : holders) { 1690 if (!McVGuiUtils.isUIHolder(holder)) { 1691 newHolders.add(holder); 1692 } 1693 } 1694 1695 g.setDisplayComponents(newHolders); 1696 } 1697 1698 newWin.setPersistentComponents(persist); 1699 newList.add(newWin); 1700 } 1701 return newList; 1702 } 1703 1704 /** 1705 * Uses the {@link ViewManager ViewManagers} in {@code info} 1706 * to build a dynamic skin. 1707 * 1708 * @param info Window that needs to become a dynamic skin. 1709 * 1710 * @return {@link McvComponentHolder} containing the ViewManagers inside 1711 * {@code info}. 1712 * 1713 * @throws Exception Bubble up any XML problems. 1714 */ 1715 public McvComponentHolder makeDynSkin(final WindowInfo info) throws Exception { 1716 Document doc = XmlUtil.getDocument(SIMPLE_SKIN_TEMPLATE); 1717 Element root = doc.getDocumentElement(); 1718 1719 Element panel = XmlUtil.findElement(root, DYNSKIN_TAG_PANEL, 1720 DYNSKIN_ATTR_ID, DYNSKIN_ID_VALUE); 1721 1722 List<ViewManager> vms = info.getViewManagers(); 1723 1724 panel.setAttribute(DYNSKIN_ATTR_COLS, Integer.toString(vms.size())); 1725 1726 for (ViewManager vm : vms) { 1727 1728 Element view = doc.createElement(DYNSKIN_TAG_VIEW); 1729 1730 view.setAttribute(DYNSKIN_ATTR_CLASS, vm.getClass().getName()); 1731 view.setAttribute(DYNSKIN_ATTR_VIEWID, vm.getUniqueId()); 1732 1733 StringBuffer props = new StringBuffer(DYNSKIN_PROPS_GENERAL); 1734 1735 if (vm instanceof MapViewManager) { 1736 if (((MapViewManager)vm).getUseGlobeDisplay()) { 1737 props.append(DYNSKIN_PROPS_GLOBE); 1738 } 1739 } 1740 1741 view.setAttribute(DYNSKIN_ATTR_PROPS, props.toString()); 1742 1743 panel.appendChild(view); 1744 1745 UIManager.savedViewManagers.put(vm.getViewDescriptor().getName(), vm); 1746 } 1747 1748 McvComponentHolder holder = 1749 new McvComponentHolder(getIdv(), XmlUtil.toString(root)); 1750 1751 holder.setType(McvComponentHolder.TYPE_DYNAMIC_SKIN); 1752 holder.setName(DYNSKIN_TMPNAME); 1753 holder.doMakeContents(); 1754 return holder; 1755 } 1756 1757 public static IdvWindow buildDynamicSkin(int width, int height, int rows, int cols, boolean showWidgets, List<PyObject> panelTypes) throws Exception { 1758 String skinTemplate; 1759 if (showWidgets) { 1760 skinTemplate = SIMPLE_SKIN_TEMPLATE; 1761 } else { 1762 skinTemplate = BUILDWINDOW_SKIN_TEMPLATE; 1763 } 1764 Document doc = XmlUtil.getDocument(skinTemplate); 1765 Element root = doc.getDocumentElement(); 1766 Element panel = XmlUtil.findElement(root, DYNSKIN_TAG_PANEL, DYNSKIN_ATTR_ID, DYNSKIN_ID_VALUE); 1767 panel.setAttribute(DYNSKIN_ATTR_ROWS, Integer.toString(rows)); 1768 panel.setAttribute(DYNSKIN_ATTR_COLS, Integer.toString(cols)); 1769 Element view = doc.createElement(DYNSKIN_TAG_VIEW); 1770 for (PyObject panelType : panelTypes) { 1771 String panelTypeRepr = panelType.__repr__().toString(); 1772 Element node = doc.createElement(IdvUIManager.COMP_VIEW); 1773 StringBuilder props; 1774 if (showWidgets) { 1775 props = new StringBuilder(DYNSKIN_PROPS_GENERAL); 1776 } else { 1777 props = new StringBuilder(BUILDWINDOW_PROPS_GENERAL); 1778 } 1779 props.append("size=").append(width).append(':').append(height).append(';'); 1780 if ("MAP".equals(panelTypeRepr)) { 1781 node.setAttribute(IdvXmlUi.ATTR_CLASS, "ucar.unidata.idv.MapViewManager"); 1782 } else if ("GLOBE".equals(panelTypeRepr)) { 1783 node.setAttribute(IdvXmlUi.ATTR_CLASS, "ucar.unidata.idv.MapViewManager"); 1784 props.append(DYNSKIN_PROPS_GLOBE); 1785 } else if ("TRANSECT".equals(panelTypeRepr)) { 1786 node.setAttribute(IdvXmlUi.ATTR_CLASS, "ucar.unidata.idv.TransectViewManager"); 1787 } else if ("MAP2D".equals(panelTypeRepr)) { 1788 node.setAttribute(IdvXmlUi.ATTR_CLASS, "ucar.unidata.idv.MapViewManager"); 1789 props.append("use3D=false;"); 1790 } 1791 view.setAttribute(DYNSKIN_ATTR_PROPS, props.toString()); 1792 view.appendChild(node); 1793 } 1794 panel.appendChild(view); 1795 UIManager uiManager = (UIManager)McIDASV.getStaticMcv().getIdvUIManager(); 1796 String skinPath; 1797 if (showWidgets) { 1798 skinPath = Constants.BLANK_COMP_GROUP; 1799 } else { 1800 skinPath = BUILDWINDOW_COMP_GROUP_HIDE_WIDGETS; 1801 } 1802 Element skinRoot = XmlUtil.getRoot(skinPath, PersistenceManager.class); 1803 IdvWindow window = uiManager.createNewWindow(null, false, "McIDAS-V", skinPath, skinRoot, false, null); 1804 ComponentGroup group = window.getComponentGroups().get(0); 1805 McvComponentHolder holder = new McvComponentHolder(McIDASV.getStaticMcv(), XmlUtil.toString(root)); 1806 holder.setType(McvComponentHolder.TYPE_DYNAMIC_SKIN); 1807 group.addComponent(holder); 1808 return window; 1809 } 1810 1811 private static final String DYNSKIN_TMPNAME = "McIDAS-V buildWindow"; 1812 private static final String DYNSKIN_TAG_PANEL = "panel"; 1813 private static final String DYNSKIN_TAG_VIEW = "idv.view"; 1814 private static final String DYNSKIN_ATTR_ID = "id"; 1815 private static final String DYNSKIN_ATTR_COLS = "cols"; 1816 private static final String DYNSKIN_ATTR_ROWS = "rows"; 1817 private static final String DYNSKIN_ATTR_PROPS = "properties"; 1818 private static final String DYNSKIN_ATTR_CLASS = "class"; 1819 private static final String DYNSKIN_ATTR_VIEWID = "viewid"; 1820 private static final String DYNSKIN_PROPS_GLOBE = "useGlobeDisplay=true;initialMapResources=/edu/wisc/ssec/mcidasv/resources/maps.xml;"; 1821 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%;"; 1822 private static final String DYNSKIN_ID_VALUE = "mcv.content"; 1823 1824 /** XML template for generating dynamic skins. */ 1825 private static final String SIMPLE_SKIN_TEMPLATE = 1826 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + 1827 "<skin embedded=\"true\">\n" + 1828 " <ui>\n" + 1829 " <panel layout=\"border\" bgcolor=\"red\">\n" + 1830 " <idv.menubar place=\"North\"/>\n" + 1831 " <panel layout=\"border\" place=\"Center\">\n" + 1832 " <panel layout=\"flow\" place=\"North\">\n" + 1833 " <idv.toolbar id=\"idv.toolbar\" place=\"West\"/>\n" + 1834 " <panel id=\"idv.favoritesbar\" place=\"North\"/>\n" + 1835 " </panel>\n" + 1836 " <panel embeddednode=\"true\" id=\"mcv.content\" layout=\"grid\" place=\"Center\">\n" + 1837 " </panel>" + 1838 " </panel>\n" + 1839 " <component idref=\"bottom_bar\"/>\n" + 1840 " </panel>\n" + 1841 " </ui>\n" + 1842 " <styles>\n" + 1843 " <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" + 1844 " <style class=\"textbtn\" space=\"2\" mouse_enter=\"ui.setText(idv.messagelabel,prop:tooltip)\" mouse_exit=\"ui.setText(idv.messagelabel,)\"/>\n" + 1845 " </styles>\n" + 1846 " <components>\n" + 1847 " <idv.statusbar place=\"South\" id=\"bottom_bar\"/>\n" + 1848 " </components>\n" + 1849 " <properties>\n" + 1850 " <property name=\"icon.wait.wait\" value=\"/ucar/unidata/idv/images/wait.gif\"/>\n" + 1851 " </properties>\n" + 1852 "</skin>\n"; 1853 1854 private static final String BUILDWINDOW_COMP_GROUP_HIDE_WIDGETS = 1855 "/edu/wisc/ssec/mcidasv/resources/skins/window/buildwindow-hidewidgets.xml"; 1856 1857 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%;"; 1858 1859 /** Dynamic skin template for buildWindow. */ 1860 private static final String BUILDWINDOW_SKIN_TEMPLATE = 1861 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + 1862 "<skin embedded=\"true\">\n" + 1863 " <ui>\n" + 1864 " <panel layout=\"border\" bgcolor=\"red\">\n" + 1865 " <panel layout=\"border\" place=\"Center\">\n" + 1866 " <panel embeddednode=\"true\" id=\"mcv.content\" layout=\"grid\" place=\"Center\">\n" + 1867 " </panel>" + 1868 " </panel>\n" + 1869 " </panel>\n" + 1870 " </ui>\n" + 1871 " <properties>\n" + 1872 " <property name=\"icon.wait.wait\" value=\"/ucar/unidata/idv/images/wait.gif\"/>\n" + 1873 " </properties>\n" + 1874 "</skin>\n"; 1875 1876 /** 1877 * Write the parameter sets 1878 */ 1879 public void writeParameterSets() { 1880 if (parameterSets != null) { 1881 1882 //DAVEP: why is our write failing? 1883 if (!parameterSets.hasWritableResource()) { 1884 logger.trace("lost writable resource"); 1885 } 1886 1887 try { 1888 parameterSets.writeWritable(); 1889 } catch (IOException exc) { 1890 LogUtil.logException("Error writing " + parameterSets.getDescription(), exc); 1891 } 1892 1893 parameterSets.setWritableDocument(parameterSetsDocument, parameterSetsRoot); 1894 } 1895 } 1896 1897 /** 1898 * Get the node representing the parameterType 1899 * 1900 * @param parameterType What type of parameter set 1901 * 1902 * @return Element representing parameterType node 1903 */ 1904 private Element getParameterTypeNode(String parameterType) { 1905 if (parameterSets == null) { 1906 parameterSets = getIdv().getResourceManager().getXmlResources(ResourceManager.RSC_PARAMETERSETS); 1907 if (parameterSets.hasWritableResource()) { 1908 parameterSetsDocument = parameterSets.getWritableDocument("<parametersets></parametersets>"); 1909 parameterSetsRoot = parameterSets.getWritableRoot("<parametersets></parametersets>"); 1910 } else { 1911 logger.trace("no writable resource found"); 1912 return null; 1913 } 1914 } 1915 1916 Element parameterTypeNode = null; 1917 try { 1918 List<Element> rootTypes = XmlUtil.findChildren(parameterSetsRoot, parameterType); 1919 if (rootTypes.isEmpty()) { 1920 parameterTypeNode = parameterSetsDocument.createElement(parameterType); 1921 parameterSetsRoot.appendChild(parameterTypeNode); 1922 logger.trace("created new '{}' node", parameterType); 1923 writeParameterSets(); 1924 } 1925 else if (rootTypes.size() == 1) { 1926 parameterTypeNode = rootTypes.get(0); 1927 logger.trace("found existing '{}' node", parameterType); 1928 } 1929 } catch (Exception exc) { 1930 LogUtil.logException("Error loading " + parameterSets.getDescription(), exc); 1931 } 1932 return parameterTypeNode; 1933 } 1934 1935 /** 1936 * Get a list of all of the categories for the given parameterType 1937 * 1938 * @param parameterType What type of parameter set 1939 * 1940 * @return List of (String) categories 1941 */ 1942 public List<String> getAllParameterSetCategories(String parameterType) { 1943 List<String> allCategories = new ArrayList<>(); 1944 try { 1945 Element rootType = getParameterTypeNode(parameterType); 1946 if (rootType != null) { 1947 allCategories = 1948 XmlUtil.findDescendantNamesWithSeparator(rootType, TAG_FOLDER, CATEGORY_SEPARATOR); 1949 } 1950 } catch (Exception exc) { 1951 LogUtil.logException("Error loading " + parameterSets.getDescription(), exc); 1952 } 1953 return allCategories; 1954 } 1955 1956 1957 /** 1958 * Get the list of {@link ParameterSet}s that are writable 1959 * 1960 * @param parameterType The type of parameter set 1961 * 1962 * @return List of writable parameter sets 1963 */ 1964 public List<ParameterSet> getAllParameterSets(String parameterType) { 1965 List<ParameterSet> allParameterSets = new ArrayList<>(); 1966 try { 1967 Element rootType = getParameterTypeNode(parameterType); 1968 if (rootType != null) { 1969 List<String> defaults = 1970 XmlUtil.findDescendantNamesWithSeparator(rootType, TAG_DEFAULT, CATEGORY_SEPARATOR); 1971 1972 for (final String aDefault : defaults) { 1973 Element anElement = XmlUtil.getElementAtNamedPath(rootType, stringToCategories(aDefault)); 1974 List<String> defaultParts = stringToCategories(aDefault); 1975 int lastIndex = defaultParts.size() - 1; 1976 String defaultName = defaultParts.get(lastIndex); 1977 defaultParts.remove(lastIndex); 1978 String folderName = StringUtil.join(CATEGORY_SEPARATOR, defaultParts); 1979 ParameterSet newSet = new ParameterSet(defaultName, folderName, parameterType, anElement); 1980 allParameterSets.add(newSet); 1981 } 1982 } 1983 } catch (Exception exc) { 1984 LogUtil.logException("Error loading " + ResourceManager.RSC_PARAMETERSETS.getDescription(), exc); 1985 } 1986 return allParameterSets; 1987 } 1988 1989 /** 1990 * Add the directory. 1991 * 1992 * @param parameterType Type of parameter set. 1993 * @param category Category (really a {@literal ">"} delimited string). 1994 * 1995 * @return {@code true} if the create was successful. {@code false} if 1996 * there already is a category with that name 1997 */ 1998 public boolean addParameterSetCategory(String parameterType, String category) { 1999 logger.trace("parameter type: '{}' category: '{}'", parameterType, category); 2000 Element rootType = getParameterTypeNode(parameterType); 2001 XmlUtil.makeElementAtNamedPath(rootType, stringToCategories(category), TAG_FOLDER); 2002 writeParameterSets(); 2003 return true; 2004 } 2005 2006 /** 2007 * Delete the given parameter set 2008 * 2009 * @param parameterType The type of parameter set 2010 * @param set Parameter set to delete. 2011 */ 2012 public void deleteParameterSet(String parameterType, ParameterSet set) { 2013 Element parameterElement = set.getElement(); 2014 Node parentNode = parameterElement.getParentNode(); 2015 parentNode.removeChild((Node)parameterElement); 2016 writeParameterSets(); 2017 } 2018 2019 /** 2020 * Delete the directory and all of its contents that the given category 2021 * represents. 2022 * 2023 * @param parameterType Type of parameter set. 2024 * @param category Category (really a {@literal ">"} delimited string). 2025 */ 2026 public void deleteParameterSetCategory(String parameterType, String category) { 2027 Element rootType = getParameterTypeNode(parameterType); 2028 Element parameterSetElement = XmlUtil.getElementAtNamedPath(rootType, stringToCategories(category)); 2029 Node parentNode = parameterSetElement.getParentNode(); 2030 parentNode.removeChild((Node)parameterSetElement); 2031 writeParameterSets(); 2032 } 2033 2034 /** 2035 * Rename the parameter set. 2036 * 2037 * @param parameterType Type of parameter set. 2038 * @param set Parameter set. 2039 */ 2040 public void renameParameterSet(String parameterType, ParameterSet set) { 2041 String name = set.getName(); 2042 Element parameterElement = set.getElement(); 2043// while (true) { 2044 name = GuiUtils.getInput("Enter a new name", "Name: ", name); 2045 if (name == null) { 2046 return; 2047 } 2048 name = StringUtil.replaceList(name.trim(), 2049 new String[] { "<", ">", "/", "\\", "\"" }, 2050 new String[] { "_", "_", "_", "_", "_" } 2051 ); 2052 if (name.length() == 0) { 2053 return; 2054 } 2055// } 2056 parameterElement.setAttribute("name", name); 2057 writeParameterSets(); 2058 } 2059 2060 /** 2061 * Move the bundle to the given category area. 2062 * 2063 * @param parameterType Type of parameter set. 2064 * @param set Parameter set. 2065 * @param categories Where to move to. 2066 */ 2067 public void moveParameterSet(String parameterType, ParameterSet set, List categories) { 2068 Element rootType = getParameterTypeNode(parameterType); 2069 Element parameterElement = set.getElement(); 2070 Node parentNode = parameterElement.getParentNode(); 2071 parentNode.removeChild((Node)parameterElement); 2072 Node newParentNode = XmlUtil.getElementAtNamedPath(rootType, categories); 2073 newParentNode.appendChild(parameterElement); 2074 writeParameterSets(); 2075 } 2076 2077 /** 2078 * Move the bundle category. 2079 * 2080 * @param parameterType Type of parameter set. 2081 * @param fromCategories Category to move. 2082 * @param toCategories Where to move to. 2083 */ 2084 public void moveParameterSetCategory(String parameterType, List fromCategories, List toCategories) { 2085 Element rootType = getParameterTypeNode(parameterType); 2086 Element parameterSetElementFrom = XmlUtil.getElementAtNamedPath(rootType, fromCategories); 2087 Node parentNode = parameterSetElementFrom.getParentNode(); 2088 parentNode.removeChild((Node)parameterSetElementFrom); 2089 Node parentNodeTo = (Node)XmlUtil.getElementAtNamedPath(rootType, toCategories); 2090 parentNodeTo.appendChild(parameterSetElementFrom); 2091 writeParameterSets(); 2092 } 2093 2094 /** 2095 * Show the Save Parameter Set dialog. 2096 * 2097 * @param parameterType Type of parameter set. 2098 * @param parameterValues Values to save. 2099 * 2100 * @return Whether or not the parameter set was saved. 2101 */ 2102 public boolean saveParameterSet(String parameterType, Hashtable parameterValues) { 2103 try { 2104 String title = "Save Parameter Set"; 2105 2106 // Create the category dropdown 2107 List<String> categories = getAllParameterSetCategories(parameterType); 2108 final JComboBox catBox = new JComboBox(); 2109 catBox.setToolTipText( 2110 "<html>Categories can be entered manually. <br>Use '>' as the category delimiter. e.g.:<br>General > Subcategory</html>"); 2111 catBox.setEditable(true); 2112 McVGuiUtils.setComponentWidth(catBox, McVGuiUtils.ELEMENT_DOUBLE_WIDTH); 2113 GuiUtils.setListData(catBox, categories); 2114 2115 // Create the default name dropdown 2116 final JComboBox nameBox = new JComboBox(); 2117 nameBox.setEditable(true); 2118 2119 List<ParameterSet> pSets = getAllParameterSets(parameterType); 2120 List tails = new ArrayList(pSets.size() * 2); 2121 for (int i = 0; i < pSets.size(); i++) { 2122 ParameterSet pSet = pSets.get(i); 2123 tails.add(new TwoFacedObject(pSet.getName(), pSet)); 2124 } 2125 java.util.Collections.sort(tails); 2126 2127 tails.add(0, new TwoFacedObject("", null)); 2128 GuiUtils.setListData(nameBox, tails); 2129 nameBox.addActionListener(new ActionListener() { 2130 public void actionPerformed(ActionEvent ae) { 2131 Object selected = nameBox.getSelectedItem(); 2132 if ( !(selected instanceof TwoFacedObject)) { 2133 return; 2134 } 2135 TwoFacedObject tfo = (TwoFacedObject) selected; 2136 List cats = ((ParameterSet) tfo.getId()).getCategories(); 2137 // if ((cats.size() > 0) && !catSelected) { 2138 if (!cats.isEmpty()) { 2139 catBox.setSelectedItem( 2140 StringUtil.join(CATEGORY_SEPARATOR, cats)); 2141 } 2142 } 2143 }); 2144 2145 JPanel panel = McVGuiUtils.sideBySide( 2146 McVGuiUtils.makeLabeledComponent("Category:", catBox), 2147 McVGuiUtils.makeLabeledComponent("Name:", nameBox) 2148 ); 2149 2150 String name = ""; 2151 String category = ""; 2152 while (true) { 2153 if ( !GuiUtils.askOkCancel(title, panel)) { 2154 return false; 2155 } 2156 name = StringUtil.replaceList(nameBox.getSelectedItem().toString().trim(), 2157 new String[] { "<", ">", "/", "\\", "\"" }, 2158 new String[] { "_", "_", "_", "_", "_" } 2159 ); 2160 if (name.isEmpty()) { 2161 LogUtil.userMessage("Please enter a name"); 2162 continue; 2163 } 2164 category = StringUtil.replaceList(catBox.getSelectedItem().toString().trim(), 2165 new String[] { "/", "\\", "\"" }, 2166 new String[] { "_", "_", "_" } 2167 ); 2168 if (category.isEmpty()) { 2169 LogUtil.userMessage("Please enter a category"); 2170 continue; 2171 } 2172 break; 2173 } 2174 2175 // Create a new element from the hashtable 2176 Element rootType = getParameterTypeNode(parameterType); 2177 Element parameterElement = parameterSetsDocument.createElement(TAG_DEFAULT); 2178 for (Enumeration e = parameterValues.keys(); e.hasMoreElements(); ) { 2179 Object nextKey = e.nextElement(); 2180 String attribute = (String)nextKey; 2181 String value = (String)parameterValues.get(nextKey); 2182 parameterElement.setAttribute(attribute, value); 2183 } 2184 2185 // Set the name to the one we entered 2186 parameterElement.setAttribute(ATTR_NAME, name); 2187 2188 Element categoryNode = XmlUtil.makeElementAtNamedPath(rootType, stringToCategories(category), TAG_FOLDER); 2189// Element categoryNode = XmlUtil.getElementAtNamedPath(rootType, stringToCategories(category)); 2190 2191 categoryNode.appendChild(parameterElement); 2192 writeParameterSets(); 2193 } 2194 catch (Exception e) { 2195 logger.error("error while saving parameter set", e); 2196 return false; 2197 } 2198 2199 return true; 2200 } 2201 2202}