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