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