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