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