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