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