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 }