001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
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.control;
030
031import java.awt.BorderLayout;
032import java.awt.Color;
033import java.awt.Component;
034import java.awt.Container;
035import java.awt.Dimension;
036import java.awt.FlowLayout;
037import java.awt.event.ActionEvent;
038import java.awt.event.ActionListener;
039import java.awt.event.MouseEvent;
040import java.rmi.RemoteException;
041import java.util.ArrayList;
042import java.util.Hashtable;
043import java.util.List;
044
045import javax.swing.JButton;
046import javax.swing.JComponent;
047import javax.swing.JFrame;
048import javax.swing.JLabel;
049import javax.swing.JMenu;
050import javax.swing.JMenuItem;
051import javax.swing.JPanel;
052import javax.swing.JPopupMenu;
053import javax.swing.JTabbedPane;
054import javax.swing.JTextField;
055import javax.swing.event.ChangeEvent;
056import javax.swing.event.ChangeListener;
057
058import org.slf4j.Logger;
059import org.slf4j.LoggerFactory;
060import org.w3c.dom.Document;
061import org.w3c.dom.Element;
062import org.w3c.dom.Node;
063
064import ucar.unidata.data.DataChoice;
065import ucar.unidata.data.DataSelection;
066import ucar.unidata.data.DataSourceImpl;
067import ucar.unidata.data.imagery.AddeImageDescriptor;
068import ucar.unidata.data.imagery.BandInfo;
069import ucar.unidata.idv.ControlContext;
070import ucar.unidata.idv.IdvResourceManager;
071import ucar.unidata.idv.control.DisplayControlImpl;
072import ucar.unidata.idv.control.McVHistogramWrapper;
073import ucar.unidata.ui.XmlTree;
074import ucar.unidata.util.ColorTable;
075import ucar.unidata.util.GuiUtils;
076import ucar.unidata.util.Range;
077import ucar.unidata.xml.XmlResourceCollection;
078import ucar.unidata.xml.XmlUtil;
079
080import visad.Data;
081import visad.DateTime;
082import visad.FieldImpl;
083import visad.FlatField;
084import visad.VisADException;
085import visad.meteorology.ImageSequenceImpl;
086
087import edu.wisc.ssec.mcidasv.PersistenceManager;
088import edu.wisc.ssec.mcidasv.chooser.ImageParameters;
089import edu.wisc.ssec.mcidasv.data.ComboDataChoice;
090import edu.wisc.ssec.mcidasv.data.adde.AddeImageParameterDataSource;
091
092/**
093 * {@link ucar.unidata.idv.control.ImagePlanViewControl} with some McIDAS-V
094 * specific extensions. Namely parameter sets and support for inverted 
095 * parameter defaults.
096 */
097
098public class ImagePlanViewControl extends ucar.unidata.idv.control.ImagePlanViewControl {
099
100    private static final Logger logger = LoggerFactory.getLogger(ImagePlanViewControl.class);
101
102    private static final String TAG_FOLDER = "folder";
103    private static final String TAG_DEFAULT = "default";
104    private static final String ATTR_NAME = "name";
105    private static final String ATTR_SERVER = "server";
106    private static final String ATTR_POS = "POS";
107    private static final String ATTR_DAY = "DAY";
108    private static final String ATTR_TIME = "TIME";
109    private static final String ATTR_UNIT = "UNIT";
110
111    /** Command for connecting */
112    protected static final String CMD_NEWFOLDER = "cmd.newfolder";
113    protected static final String CMD_NEWPARASET = "cmd.newparaset";
114
115    /** save parameter set */
116    private JFrame saveWindow;
117
118    private static String newFolder;
119
120    private XmlTree xmlTree;
121
122    /** Holds the current save set tree */
123    private JPanel treePanel;
124
125    /** The user imagedefaults xml root */
126    private static Element imageDefaultsRoot;
127
128    /** The user imagedefaults xml document */
129    private static Document imageDefaultsDocument;
130
131    /** Holds the ADDE servers and groups*/
132    private static XmlResourceCollection imageDefaults;
133
134    private Node lastCat;
135
136    private static Element lastClicked;
137
138    private JButton newFolderBtn;
139
140    private JButton newSetBtn;
141
142    private String newCompName = "";
143
144    /** Shows the status */
145    private JLabel statusLabel;
146
147    /** Status bar component */
148    private JComponent statusComp;
149
150    private JPanel contents;
151
152    private DataSourceImpl dataSource;
153
154    private FlatField image;
155    
156    final JTextField minBox = new JTextField(6);
157    final JTextField maxBox = new JTextField(6);
158    
159    private float rangeMin;
160    private float rangeMax;
161    
162    private float origRangeMin;
163    private float origRangeMax;
164
165    public ImagePlanViewControl() {
166        super();
167        logger.trace("created new imageplanviewcontrol={}", Integer.toHexString(hashCode()));
168        ImagePlanViewControl.imageDefaults = getImageDefaults();
169    }
170
171    @Override public boolean init(DataChoice dataChoice) 
172        throws VisADException, RemoteException 
173    {
174        // TJJ Jul 2014
175        // by sharing a property via the active View Manager, we can signal any 
176        // preview windows they are part of an in-progress Formula. If so, it 
177        // appears we need to use a shared HydraContext so our geographic coverage
178        // subset applies across channels, and so we use full res data.  
179        
180        Hashtable ht = getIdv().getViewManager().getProperties();
181        ht.put(RGBCompositeControl.FORMULA_IN_PROGRESS_FLAG, true);
182        boolean result = super.init((DataChoice)this.getDataChoices().get(0));
183        ht.put(RGBCompositeControl.FORMULA_IN_PROGRESS_FLAG, false);
184        return result;
185    }
186
187    /**
188     * Get the xml resource collection that defines the image default xml
189     *
190     * @return Image defaults resources
191     */
192    protected XmlResourceCollection getImageDefaults() {
193        XmlResourceCollection ret = null;
194        try {
195            ControlContext controlContext = getControlContext();
196            if (controlContext != null) {
197                IdvResourceManager irm = controlContext.getResourceManager();
198                ret = irm.getXmlResources( IdvResourceManager.RSC_IMAGEDEFAULTS);
199                if (ret.hasWritableResource()) {
200                    imageDefaultsDocument =
201                        ret.getWritableDocument("<imagedefaults></imagedefaults>");
202                    imageDefaultsRoot =
203                        ret.getWritableRoot("<imagedefaults></imagedefaults>");
204                }
205            }
206        } catch (Exception e) {
207            logger.error("problem trying to set up xml document", e);
208        }
209        return ret;
210    }
211
212    /**
213     * Called by doMakeWindow in DisplayControlImpl, which then calls its
214     * doMakeMainButtonPanel(), which makes more buttons.
215     *
216     * @return container of contents
217     */
218    public Container doMakeContents() {
219        try {
220            JTabbedPane tab = new MyTabbedPane();
221            tab.add("Settings",
222                GuiUtils.inset(GuiUtils.top(doMakeWidgetComponent()), 5));
223            
224            // MH: just add a dummy component to this tab for now..
225            //            don't init histogram until the tab is clicked.
226            tab.add("Histogram", new JLabel("Histogram not yet initialized"));
227
228            return tab;
229        } catch (Exception exc) {
230            logException("doMakeContents", exc);
231        }
232        return null;
233    }
234
235    @Override public void doRemove() throws RemoteException, VisADException {
236        super.doRemove();
237    }
238    
239    /**
240     * Take out the histogram-related stuff that was in doMakeContents and put it
241     * in a standalone method, so we can wait and call it only after the
242     * histogram is actually initialized.
243     */
244    private void setInitialHistogramRange() {
245        try {
246            org.jfree.data.Range range = histoWrapper.getRange();
247            rangeMin = (float) range.getLowerBound();
248            rangeMax = (float) range.getUpperBound();
249            origRangeMin = rangeMin;
250            origRangeMax = rangeMax;
251            minBox.setText(Integer.toString((int) origRangeMin));
252            maxBox.setText(Integer.toString((int) origRangeMax));
253            histoWrapper.setHigh(rangeMax);
254            histoWrapper.setLow(rangeMin);
255        } catch (Exception exc) {
256            logException("setInitialHistogramRange", exc);
257        }
258    }
259
260    protected JComponent getHistogramTabComponent() {
261        List choices = new ArrayList();
262        if (datachoice == null) {
263            datachoice = getDataChoice();
264        }
265        choices.add(datachoice);
266        histoWrapper = new McVHistogramWrapper("Histogram", choices, (DisplayControlImpl) this);
267        dataSource = getDataSource();
268
269        if (dataSource == null) {
270            try {
271                image = (FlatField) ((ComboDataChoice) datachoice).getData();
272                histoWrapper.loadData(image);
273            } catch (IllegalArgumentException e) {
274                logger.trace("Could not create histogram: nothing to show!", e);
275            } catch (RemoteException | VisADException e) {
276                logger.error("Could not create histogram!", e);
277            }
278        } else {
279            Hashtable props = dataSource.getProperties();
280            try {
281                DataSelection testSelection = datachoice.getDataSelection();
282                DataSelection realSelection = getDataSelection();
283                if (testSelection == null) {
284                    datachoice.setDataSelection(realSelection);
285                }
286                ImageSequenceImpl seq = null;
287                if (dataSelection == null)
288                    dataSelection = dataSource.getDataSelection();
289                if (dataSelection == null) {
290                    image = (FlatField)dataSource.getData(datachoice, null, props);
291                    if (image == null) {
292                        image = (FlatField)datachoice.getData(null);
293                    }
294                } else {
295                    Data data = dataSource.getData(datachoice, null, dataSelection, props);
296                    if (data instanceof ImageSequenceImpl) {
297                        seq = (ImageSequenceImpl) data;
298                    } else if (data instanceof FlatField) {
299                        image = (FlatField) data;
300                    } else if (data instanceof FieldImpl) {
301                        image = (FlatField) ((FieldImpl)data).getSample(0, false);
302                    } else {
303                        throw new Exception("Histogram must be made from a FlatField");
304                    }
305                }
306                if ((seq != null) && (seq.getImageCount() > 0)) {
307                    image = (FlatField)seq.getImage(0);
308                }
309                try {
310                    histoWrapper.loadData(image);
311                } catch (IllegalArgumentException e) {
312                    logger.trace("Could not create histogram: nothing to show!", e);
313                }
314            } catch (Exception e) {
315                logger.error("attempting to set up histogram", e);
316            }
317        }
318
319        JLabel minLabel = new JLabel("Range Min");
320        JLabel maxLabel = new JLabel("Range Max");
321        
322        minBox.addActionListener(ae -> {
323            rangeMin = Float.valueOf(minBox.getText().trim());
324            rangeMax = Float.valueOf(maxBox.getText().trim());
325            histoWrapper.modifyRange((int) rangeMin, (int) rangeMax);
326        });
327        maxBox.addActionListener(ae -> {
328            rangeMin = Float.valueOf(minBox.getText().trim());
329            rangeMax = Float.valueOf(maxBox.getText().trim());
330            histoWrapper.modifyRange((int) rangeMin, (int) rangeMax);
331        });
332        
333        JComponent histoComp = histoWrapper.doMakeContents();
334        JButton resetButton = new JButton("Reset");
335        resetButton.addActionListener(new ActionListener() {
336            public void actionPerformed(ActionEvent ae) {
337                resetColorTable();
338            }
339        });
340        JPanel resetPanel = new JPanel(new FlowLayout());
341        resetPanel.add(minLabel);
342        resetPanel.add(minBox);
343        resetPanel.add(maxLabel);
344        resetPanel.add(maxBox);
345        resetPanel.add(resetButton);
346        return GuiUtils.centerBottom(histoComp, resetPanel);
347    }
348
349    protected void contrastStretch(double low, double high) {
350        ColorTable ct = getColorTable();
351        if (ct != null) {
352            Range range = new Range(low, high);
353            try {
354                setRange(ct.getName(), range);
355            } catch (Exception e) {
356                logger.error("problem stretching contrast", e);
357            }
358        }
359    }
360
361    @Override public boolean setData(DataChoice dataChoice) throws VisADException, RemoteException {
362        boolean result = super.setData(dataChoice);
363//        logger.trace("result: {}, dataChoice: {}", result, dataChoice);
364        return result;
365    }
366
367    @Override public void setRange(final Range newRange) throws RemoteException, VisADException {
368//        logger.trace("newRange: {} [avoiding NPE!]", newRange);
369        super.setRange(newRange);
370        if (histoWrapper != null) {
371            histoWrapper.modifyRange(newRange.getMin(), newRange.getMax(), false);
372        }
373    }
374        
375    public void resetColorTable() {
376        try {
377            revertToDefaultColorTable();
378            revertToDefaultRange();
379            histoWrapper.doReset();
380            histoWrapper.modifyRange((int) origRangeMin, (int) origRangeMax);
381            minBox.setText(Integer.toString((int) origRangeMin));
382            maxBox.setText(Integer.toString((int) origRangeMax));
383        } catch (Exception e) {
384            logger.error("problem resetting color table", e);
385        }
386    }
387
388    protected void getSaveMenuItems(List items, boolean forMenuBar) {
389        super.getSaveMenuItems(items, forMenuBar);
390
391        // DAVEP: Remove the parameter set save options for now...
392//        items.add(GuiUtils.makeMenuItem("Save Image Parameter Set (TEST)", this,
393//        "popupPersistImageParameters"));
394//
395//        items.add(GuiUtils.makeMenuItem("Save Image Parameter Set", this,
396//        "popupSaveImageParameters"));
397        
398        items.add(GuiUtils.makeMenuItem("Save As Local Data Source", this,
399        "saveDataToLocalDisk"));
400    }
401
402    public void popupPersistImageParameters() {
403        PersistenceManager pm = (PersistenceManager)getIdv().getPersistenceManager();
404        pm.saveParameterSet("addeimagery", makeParameterValues());
405    }
406
407    private Hashtable makeParameterValues() {
408        Hashtable parameterValues = new Hashtable();
409//        Document doc = XmlUtil.makeDocument();
410//        Element newChild = doc.createElement(TAG_DEFAULT);
411
412        if (datachoice == null) {
413            datachoice = getDataChoice();
414        }
415        dataSource = getDataSource();
416        if (!(dataSource.getClass().isInstance(new AddeImageParameterDataSource()))) {
417            logger.trace("dataSource not a AddeImageParameterDataSource; it is: {}", dataSource.getClass().toString());
418            return parameterValues;
419        }
420        AddeImageParameterDataSource testDataSource = (AddeImageParameterDataSource)dataSource;
421        List imageList = testDataSource.getDescriptors(datachoice, this.dataSelection);
422        int numImages = imageList.size();
423        List dateTimes = new ArrayList();
424        DateTime thisDT = null;
425        if (!(imageList == null)) {
426            AddeImageDescriptor aid = null;
427            for (int imageNo=0; imageNo<numImages; imageNo++) {
428                aid = (AddeImageDescriptor) imageList.get(imageNo);
429                thisDT = aid.getImageTime();
430                if (!dateTimes.contains(thisDT)) {
431                    if (thisDT != null) {
432                        dateTimes.add(thisDT);
433                    }
434                }
435            }
436
437            // Set the date and time for later reference
438            String dateS = "";
439            String timeS = "";
440            if (!dateTimes.isEmpty()) {
441                thisDT = (DateTime)dateTimes.get(0);
442                dateS = thisDT.dateString();
443                timeS = thisDT.timeString();
444                if (dateTimes.size() > 1) {
445                    for (int img=1; img<dateTimes.size(); img++) {
446                        thisDT = (DateTime)dateTimes.get(img);
447                        String str = "," + thisDT.dateString();
448                        String newString = dateS + str;
449                        dateS = newString;
450                        str = "," + thisDT.timeString();
451                        newString = timeS + str;
452                        timeS = newString;
453                    }
454                }
455            }
456
457            // Set the unit for later reference
458            String unitS = "";
459            if (!(datachoice.getId() instanceof BandInfo)) {
460                logger.trace("dataChoice ID not a BandInfo; it is: {}", datachoice.getId().getClass().toString());
461                return parameterValues;
462            }
463            BandInfo bi = (BandInfo)datachoice.getId();
464            unitS = bi.getPreferredUnit();
465
466            if (aid != null) {
467                String displayUrl = testDataSource.getDisplaySource();
468                ImageParameters ip = new ImageParameters(displayUrl);
469                List props = ip.getProperties();
470                List vals = ip.getValues();
471                String server = ip.getServer();
472                parameterValues.put(ATTR_SERVER, server);
473//                newChild.setAttribute(ATTR_SERVER, server);
474                int num = props.size();
475                if (num > 0) {
476                    String attr = "";
477                    String val = "";
478                    for (int i=0; i<num; i++) {
479                        attr = (String)(props.get(i));
480                        if (attr.equals(ATTR_POS)) {
481                            val = new Integer(numImages - 1).toString();
482                        } else if (attr.equals(ATTR_DAY)) {
483                            val = dateS;
484                        } else if (attr.equals(ATTR_TIME)) {
485                            val = timeS;
486                        } else if (attr.equals(ATTR_UNIT)) {
487                            val = unitS;
488                        } else {
489                            val = (String)(vals.get(i));
490                        }
491                        parameterValues.put(attr, val);
492                    }
493                }
494            }
495        }
496        return parameterValues;
497    }
498
499    public void saveDataToLocalDisk() {
500        getDataSource().saveDataToLocalDisk();
501    }
502
503    public void popupSaveImageParameters() {
504        if (saveWindow == null) {
505            showSaveDialog();
506            return;
507        }
508        saveWindow.setVisible(true);
509        GuiUtils.toFront(saveWindow);
510    }
511
512    private void showSaveDialog() {
513        if (saveWindow == null) {
514            saveWindow = GuiUtils.createFrame("Save Image Parameter Set");
515        }
516        if (statusComp == null) {
517            statusLabel = new JLabel();
518            statusComp = GuiUtils.inset(statusLabel, 2);
519            statusComp.setBackground(new Color(255, 255, 204));
520            statusLabel.setOpaque(true); 
521            statusLabel.setBackground(new Color(255, 255, 204));
522        }
523        JPanel statusPanel = GuiUtils.inset(GuiUtils.top( GuiUtils.vbox(new JLabel(" "),
524            GuiUtils.hbox(GuiUtils.rLabel("Status: "), statusComp),
525            new JLabel(" "))), 6);
526        JPanel sPanel = GuiUtils.topCenter(statusPanel, GuiUtils.filler());
527
528        List newComps = new ArrayList();
529        final JTextField newName = new JTextField(20);
530        newName.addActionListener(new ActionListener() {
531            public void actionPerformed(ActionEvent ae) {
532                setStatus("Click New Folder or New ParameterSet button");
533                newCompName = newName.getText().trim();
534            }
535        });
536        newComps.add(newName);
537        newComps.add(GuiUtils.filler());
538        newFolderBtn = new JButton("New Folder");
539        newFolderBtn.addActionListener(new ActionListener() {
540            public void actionPerformed(ActionEvent ae) {
541                newFolder = newName.getText().trim();
542                if (newFolder.length() == 0) {
543                    newComponentError("folder");
544                    return;
545                }
546                Element exists = XmlUtil.findElement(imageDefaultsRoot, "folder", ATTR_NAME, newFolder);
547                if (!(exists == null)) {
548                    if (!GuiUtils.askYesNo("Verify Replace Folder",
549                        "Do you want to replace the folder " +
550                        "\"" + newFolder + "\"?" +
551                    "\nNOTE: All parameter sets it contains will be deleted.")) return;
552                    imageDefaultsRoot.removeChild(exists);
553                }
554                newName.setText("");
555                Node newEle = makeNewFolder();
556                makeXmlTree();
557                xmlTree.selectElement((Element)newEle);
558                lastCat = newEle;
559                lastClicked = null;
560                newSetBtn.setEnabled(true);
561                setStatus("Please enter a name for the new parameter set");
562            }
563        });
564        newComps.add(newFolderBtn);
565        newComps.add(GuiUtils.filler());
566        newName.setEnabled(true);
567        newFolderBtn.setEnabled(true);
568        newSetBtn = new JButton("New Parameter Set");
569        newSetBtn.setActionCommand(CMD_NEWPARASET);
570        newSetBtn.addActionListener(new ActionListener() {
571            public void actionPerformed(ActionEvent ae) {
572                newCompName = newName.getText().trim();
573                if (newCompName.length() == 0) {
574                    newComponentError("parameter set");
575                    return;
576                }
577                newName.setText("");
578                Element newEle = saveParameterSet();
579                if (newEle == null) return;
580                xmlTree.selectElement(newEle);
581                lastClicked = newEle;
582            }
583        });
584        newComps.add(newSetBtn);
585        newSetBtn.setEnabled(false);
586
587        JPanel newPanel = GuiUtils.top(GuiUtils.left(GuiUtils.hbox(newComps)));
588        JPanel topPanel = GuiUtils.topCenter(sPanel, newPanel);
589
590        treePanel = new JPanel();
591        treePanel.setLayout(new BorderLayout());
592        makeXmlTree();
593        ActionListener listener = new ActionListener() {
594            public void actionPerformed(ActionEvent event) {
595                String cmd = event.getActionCommand();
596                if (cmd.equals(GuiUtils.CMD_CANCEL)) {
597                    if (lastClicked != null) {
598                        removeNode(lastClicked);
599                        lastClicked = null;
600                    }
601                    saveWindow.setVisible(false);
602                    saveWindow = null;
603                } else {
604                    saveWindow.setVisible(false);
605                    saveWindow = null;
606                }
607            }
608        };
609        JPanel bottom =
610            GuiUtils.inset(GuiUtils.makeApplyCancelButtons(listener), 5);
611        contents = 
612            GuiUtils.topCenterBottom(topPanel, treePanel, bottom);
613
614        saveWindow.getContentPane().add(contents);
615        saveWindow.pack();
616        saveWindow.setLocation(200, 200);
617
618        saveWindow.setVisible(true);
619        GuiUtils.toFront(saveWindow);
620        setStatus("Please select a folder from tree, or create a new folder");
621    }
622
623    private void newComponentError(String comp) {
624        JLabel label = new JLabel("Please enter " + comp +" name");
625        JPanel contents = GuiUtils.top(GuiUtils.inset(label, 24));
626        GuiUtils.showOkCancelDialog(null, "Make Component Error", contents, null);
627    }
628
629    private void setStatus(String msg) {
630        statusLabel.setText(msg);
631        contents.paintImmediately(0,0,contents.getWidth(),
632            contents.getHeight());
633    }
634
635    private void removeNode(Element node) {
636        if (imageDefaults == null) {
637            imageDefaults = getImageDefaults();
638        }
639        Node parent = node.getParentNode();
640        parent.removeChild(node);
641        makeXmlTree();
642        try {
643            imageDefaults.writeWritable();
644        } catch (Exception e) {
645            logger.error("write error!", e);
646        }
647        imageDefaults.setWritableDocument(imageDefaultsDocument,
648            imageDefaultsRoot);
649    }
650
651    private Node makeNewFolder() {
652        if (imageDefaults == null) {
653            imageDefaults = getImageDefaults();
654        }
655        if (newFolder.length() == 0) {
656            return null;
657        }
658        List newChild = new ArrayList();
659        Node newEle = imageDefaultsDocument.createElement(TAG_FOLDER);
660        lastCat = newEle;
661        String[] newAttrs = { ATTR_NAME, newFolder };
662        XmlUtil.setAttributes((Element)newEle, newAttrs);
663        newChild.add(newEle);
664        XmlUtil.addChildren(imageDefaultsRoot, newChild);
665        try {
666            imageDefaults.writeWritable();
667        } catch (Exception e) {
668            logger.error("write error!", e);
669        }
670        imageDefaults.setWritableDocument(imageDefaultsDocument,
671            imageDefaultsRoot);
672        return newEle;
673    }
674
675    /**
676     * Just creates an empty XmlTree
677     */
678    private void makeXmlTree() {
679        if (imageDefaults == null) {
680            imageDefaults = getImageDefaults();
681        }
682        xmlTree = new XmlTree(imageDefaultsRoot, true, "") {
683            public void doClick(XmlTree theTree, XmlTree.XmlTreeNode node,
684                Element element) {
685                Element clicked = xmlTree.getSelectedElement();
686                String lastTag = clicked.getTagName();
687                if ("folder".equals(lastTag)) {
688                    lastCat = clicked;
689                    lastClicked = null;
690                    setStatus("Please enter a name for the new parameter set");
691                    newSetBtn.setEnabled(true);
692                } else {
693                    lastCat = clicked.getParentNode();
694                    lastClicked = clicked;
695                }
696            }
697
698            public void doRightClick(XmlTree theTree,
699                XmlTree.XmlTreeNode node,
700                Element element, MouseEvent event) {
701                JPopupMenu popup = new JPopupMenu();
702                if (makePopupMenu(theTree, element, popup)) {
703                    popup.show((Component) event.getSource(), event.getX(),
704                        event.getY());
705                }
706            }
707        };
708        List tagList = new ArrayList();
709        tagList.add(TAG_FOLDER);
710        tagList.add(TAG_DEFAULT);
711        xmlTree.addTagsToProcess(tagList);
712        xmlTree.defineLabelAttr(TAG_FOLDER, ATTR_NAME);
713        addToContents(GuiUtils.inset(GuiUtils.topCenter(new JPanel(),
714            xmlTree.getScroller()), 5));
715        return;
716    }
717
718    private List getFolders() {
719        return XmlUtil.findChildren(imageDefaultsRoot, TAG_FOLDER);
720    }
721
722
723    private void doDeleteRequest(Node node) {
724        if (node == null) {
725            return;
726        }
727        Element ele = (Element)node;
728        String tagName = ele.getTagName();
729        if (tagName.equals("folder")) {
730            if (!GuiUtils.askYesNo("Verify Delete Folder",
731                "Do you want to delete the folder " +
732                "\"" + ele.getAttribute("name") + "\"?" +
733            "\nNOTE: All parameter sets it contains will be deleted.")) return;
734            XmlUtil.removeChildren(ele);
735        } else if (tagName.equals("default")) {
736            if (!GuiUtils.askYesNo("Verify Delete", "Do you want to delete " +
737                "\"" + ele.getAttribute(ATTR_NAME) + "\"?")) return;
738        } else { return; }
739        removeNode(ele);
740    }
741    /**
742     *  Create and popup a command menu for when the user has clicked on the given xml node.
743     *
744     *  @param theTree The XmlTree object displaying the current xml document.
745     *  @param node The xml node the user clicked on.
746     *  @param popup The popup menu to put the menu items in.
747     * @return Did we add any items into the menu
748     */
749    private boolean makePopupMenu(final XmlTree theTree, final Element node,
750        JPopupMenu popup) 
751    {
752        theTree.selectElement(node);
753        String tagName = node.getTagName();
754        final Element parent = (Element)node.getParentNode();
755        boolean didone  = false;
756        JMenuItem mi;
757
758        if (tagName.equals("default")) {
759            lastClicked = node;
760            JMenu moveMenu = new JMenu("Move to");
761            List folders = getFolders();
762            for (int i = 0; i < folders.size(); i++) {
763                final Element newFolder = (Element)folders.get(i);
764                if (!newFolder.isSameNode(parent)) {
765                    String name = newFolder.getAttribute(ATTR_NAME);
766                    mi = new JMenuItem(name);
767                    mi.addActionListener(new ActionListener() {
768                        public void actionPerformed(ActionEvent ae) {
769                            moveParameterSet(parent, newFolder);
770                        }
771                    });
772                    moveMenu.add(mi);
773                }
774            }
775            popup.add(moveMenu);
776            popup.addSeparator();
777            didone = true;
778        }
779
780        mi = new JMenuItem("Rename...");
781        mi.addActionListener(new ActionListener() {
782            public void actionPerformed(ActionEvent ae) {
783                doRename(node);
784            }
785        });
786        popup.add(mi);
787        didone = true;
788
789        mi = new JMenuItem("Delete");
790        mi.addActionListener(new ActionListener() {
791            public void actionPerformed(ActionEvent ae) {
792                doDeleteRequest(node);
793            }
794        });
795        popup.add(mi);
796        didone = true;
797
798        return didone;
799    }
800
801    public void moveParameterSet(Element parent, Element newFolder) {
802        if (imageDefaults == null) {
803            imageDefaults = getImageDefaults();
804        }
805        if (lastClicked == null) {
806            return;
807        }
808        Node copyNode = lastClicked.cloneNode(true);
809        newFolder.appendChild(copyNode);
810        parent.removeChild(lastClicked);
811        lastCat = newFolder;
812        makeXmlTree();
813        try {
814            imageDefaults.writeWritable();
815        } catch (Exception e) {
816            logger.error("write error!", e);
817        }
818        imageDefaults.setWritableDocument(imageDefaultsDocument, imageDefaultsRoot);
819    }
820
821    private void doRename(Element node) {
822        if (imageDefaults == null) {
823            imageDefaults = getImageDefaults();
824        }
825        if (!node.hasAttribute(ATTR_NAME)) return;
826        JTextField nameFld = new JTextField("", 20);
827        JComponent contents = GuiUtils.doLayout(new Component[] {
828            GuiUtils.rLabel("New name: "), nameFld, }, 2,
829            GuiUtils.WT_N, GuiUtils.WT_N);
830        contents = GuiUtils.center(contents);
831        contents = GuiUtils.inset(contents, 10);
832        if (!GuiUtils.showOkCancelDialog(null, "Rename \"" +
833            node.getAttribute("name") + "\"", contents, null)) return;
834        String newName = nameFld.getText().trim();
835        String tagName = node.getTagName();
836        Element root = imageDefaultsRoot;
837        if (tagName.equals("default")) {
838            root = (Element)node.getParentNode();
839        }
840        Element exists = XmlUtil.findElement(root, tagName, ATTR_NAME, newName);
841        if (!(exists == null)) {
842            if (!GuiUtils.askYesNo("Name Already Exists",
843                "Do you want to replace " + node.getAttribute("name") + " with" +
844                "\"" + newName + "\"?")) return;
845        }
846        node.removeAttribute(ATTR_NAME);
847        node.setAttribute(ATTR_NAME, newName);
848        makeXmlTree();
849        try {
850            imageDefaults.writeWritable();
851        } catch (Exception e) {
852            logger.error("write error!", e);
853        }
854        imageDefaults.setWritableDocument(imageDefaultsDocument,
855            imageDefaultsRoot);
856    }
857
858    /**
859     *  Remove the currently display gui and insert the given one.
860     *
861     *  @param comp The new gui.
862     */
863    private void addToContents(JComponent comp) {
864        treePanel.removeAll();
865        comp.setPreferredSize(new Dimension(200, 300));
866        treePanel.add(comp, BorderLayout.CENTER);
867        if (contents != null) {
868            contents.invalidate();
869            contents.validate();
870            contents.repaint();
871        }
872    }
873
874    public DataSourceImpl getDataSource() {
875        DataSourceImpl ds = null;
876        List dataSources = getDataSources();
877        if (!dataSources.isEmpty()) {
878            ds = (DataSourceImpl)dataSources.get(0);
879        }
880        return ds;
881    }
882
883    public Element saveParameterSet() {
884        if (imageDefaults == null) {
885            imageDefaults = getImageDefaults();
886        }
887        if (newCompName.length() == 0) {
888            newComponentError("parameter set");
889            return null;
890        }
891        Element newChild = imageDefaultsDocument.createElement(TAG_DEFAULT);
892        newChild.setAttribute(ATTR_NAME, newCompName);
893
894        if (datachoice == null) {
895            datachoice = getDataChoice();
896        }
897        dataSource = getDataSource();
898        if (!(dataSource.getClass().isInstance(new AddeImageParameterDataSource()))) {
899            return newChild;
900        }
901        AddeImageParameterDataSource testDataSource = (AddeImageParameterDataSource)dataSource;
902        List imageList = testDataSource.getDescriptors(datachoice, this.dataSelection);
903        int numImages = imageList.size();
904        List dateTimes = new ArrayList();
905        DateTime thisDT = null;
906        if (!(imageList == null)) {
907            AddeImageDescriptor aid = null;
908            for (int imageNo = 0; imageNo < numImages; imageNo++) {
909                aid = (AddeImageDescriptor)(imageList.get(imageNo));
910                thisDT = aid.getImageTime();
911                if (!(dateTimes.contains(thisDT))) {
912                    if (thisDT != null) {
913                        dateTimes.add(thisDT);
914                    }
915                }
916            }
917            String dateS = "";
918            String timeS = "";
919            if (!(dateTimes.isEmpty())) {
920                thisDT = (DateTime)dateTimes.get(0);
921                dateS = thisDT.dateString();
922                timeS = thisDT.timeString();
923                if (dateTimes.size() > 1) {
924                    for (int img = 1; img < dateTimes.size(); img++) {
925                        thisDT = (DateTime)dateTimes.get(img);
926                        String str = ',' + thisDT.dateString();
927                        String newString = new String(dateS + str);
928                        dateS = newString;
929                        str = ',' + thisDT.timeString();
930                        newString = new String(timeS + str);
931                        timeS = newString;
932                    }
933                }
934            }
935            if (aid != null) {
936                String displayUrl = testDataSource.getDisplaySource();
937                ImageParameters ip = new ImageParameters(displayUrl);
938                List props = ip.getProperties();
939                List vals = ip.getValues();
940                String server = ip.getServer();
941                newChild.setAttribute(ATTR_SERVER, server);
942                int num = props.size();
943                if (num > 0) {
944                    String attr = "";
945                    String val = "";
946                    for (int i = 0; i < num; i++) {
947                        attr = (String)(props.get(i));
948                        if (attr.equals(ATTR_POS)) {
949                            val = new Integer(numImages - 1).toString();
950                        } else if (attr.equals(ATTR_DAY)) {
951                            val = dateS;
952                        } else if (attr.equals(ATTR_TIME)) {
953                            val = timeS;
954                        } else {
955                            val = (String)(vals.get(i));
956                        }
957                        newChild.setAttribute(attr, val);
958                    }
959                }
960            }
961        }
962        Element parent = xmlTree.getSelectedElement();
963        if (parent == null) {
964            parent = (Element)lastCat;
965        }
966        if (parent != null) {
967            Element exists = XmlUtil.findElement(parent, "default", ATTR_NAME, newCompName);
968            if (!(exists == null)) {
969                JLabel label = new JLabel("Replace \"" + newCompName + "\"?");
970                JPanel contents = GuiUtils.top(GuiUtils.inset(label, newCompName.length()+12));
971                if (!GuiUtils.showOkCancelDialog(null, "Parameter Set Exists", contents, null)) {
972                    return newChild;
973                }
974                parent.removeChild(exists);
975            }
976            parent.appendChild(newChild);
977            makeXmlTree();
978        }
979        try {
980            imageDefaults.writeWritable();
981        } catch (Exception e) {
982            logger.error("write error!", e);
983        }
984        imageDefaults.setWritableDocument(imageDefaultsDocument, imageDefaultsRoot);
985        return newChild;
986    }
987
988    /**
989     * Holds a JFreeChart histogram of image values.
990     */
991    private class MyTabbedPane extends JTabbedPane implements ChangeListener {
992        /** Have we been painted */
993        boolean painted = false;
994        
995        boolean haveDoneHistogramInit = false;
996
997        /**
998         * Creates a new {@code MyTabbedPane} that gets immediately registered
999         * as a {@link javax.swing.event.ChangeListener} for its own events.
1000         */
1001        public MyTabbedPane() {
1002            addChangeListener(this);
1003        }
1004        /**
1005         * The histogram isn't created unless the user selects the histogram
1006         * tab (this is done in an effort to avoid a spike in memory usage).
1007         *
1008         * @param e The event. Ignored for now.
1009         */
1010        public void stateChanged(ChangeEvent e) {
1011            // MH: don't make the histogram until user clicks the tab.
1012            int index = getSelectedIndex();
1013            if (index >= 0 && getTitleAt(index).equals("Histogram") && !haveDoneHistogramInit) {
1014                getIdv().showWaitCursor();
1015                this.setComponentAt(index,
1016                        GuiUtils.inset(getHistogramTabComponent(),5));
1017                setInitialHistogramRange();
1018                getIdv().clearWaitCursor();
1019//                haveDoneHistogramInit = true;
1020            }
1021        }
1022
1023        /**
1024         * MH: Not really doing anything useful...but will leave it here for now...
1025         *
1026         * @param g graphics
1027         */
1028        public void paint(java.awt.Graphics g) {
1029            if (!painted) {
1030                painted = true;
1031            }
1032            super.paint(g);
1033        }
1034    }
1035}