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