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