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