/*
 * GuiSupport.java
 *
 * Created on November 30, 2007, 5:04 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */
package org.autoplot;

import org.autoplot.renderer.ColorScatterStylePanel;
import org.autoplot.renderer.SpectrogramStylePanel;
import org.autoplot.renderer.SeriesStylePanel;
import org.autoplot.renderer.HugeScatterStylePanel;
import ZoeloeSoft.projects.JFontChooser.JFontChooser;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Frame;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BoxLayout;
import javax.swing.ComponentInputMap;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.InputMap;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.filechooser.FileFilter;
import org.autoplot.renderer.ContourStylePanel;
import org.autoplot.renderer.DigitalStylePanel;
import org.autoplot.renderer.EventsStylePanel;
import org.autoplot.renderer.ImageStylePanel;
import org.autoplot.renderer.OrbitStylePanel;
import org.autoplot.renderer.PitchAngleDistributionStylePanel;
import org.autoplot.renderer.StackedHistogramStylePanel;
import org.das2.DasApplication;
import org.das2.components.DasProgressPanel;
import org.das2.components.propertyeditor.PropertyEditor;
import org.das2.datum.DatumRange;
import org.das2.datum.Units;
import org.das2.datum.UnitsUtil;
import org.das2.event.DasMouseInputAdapter;
import org.das2.event.MouseModule;
import org.das2.event.PointSlopeDragRenderer;
import org.das2.graph.BorderType;
import org.das2.graph.DasAxis;
import org.das2.graph.DasCanvas;
import org.das2.graph.DasPlot;
import org.das2.system.RequestProcessor;
import org.das2.util.Entities;
import org.das2.util.awt.PdfGraphicsOutput;
import org.das2.util.monitor.NullProgressMonitor;
import org.das2.util.monitor.ProgressMonitor;
import org.autoplot.bookmarks.Bookmark;
import org.autoplot.bookmarks.BookmarksException;
import org.autoplot.dom.Annotation;
import org.autoplot.dom.Application;
import org.autoplot.dom.ApplicationController;
import org.autoplot.dom.Axis;
import org.autoplot.dom.BindingModel;
import org.autoplot.dom.Canvas;
import org.autoplot.dom.Connector;
import org.autoplot.dom.DataSourceFilter;
import org.autoplot.dom.DomUtil;
import org.autoplot.dom.OptionsPrefsController;
import org.autoplot.dom.Plot;
import org.autoplot.dom.PlotController;
import org.autoplot.dom.PlotElement;
import org.autoplot.layout.LayoutConstants;
import org.autoplot.state.StatePersistence;
import org.autoplot.transferrable.ImageSelection;
import org.das2.qds.DataSetOps;
import org.das2.qds.QDataSet;
import org.das2.qds.SemanticOps;
import org.autoplot.datasource.AutoplotSettings;
import org.autoplot.datasource.DataSetSelector;
import org.autoplot.datasource.DataSetURI;
import org.autoplot.datasource.DataSource;
import org.autoplot.datasource.DataSourceFactory;
import org.autoplot.datasource.DataSourceFormat;
import org.autoplot.datasource.DataSourceFormatEditorPanel;
import org.autoplot.datasource.DataSourceRegistry;
import org.autoplot.datasource.DataSourceUtil;
import org.autoplot.datasource.URISplit;
import org.autoplot.datasource.WindowManager;
import org.autoplot.datasource.capability.TimeSeriesBrowse;
import org.autoplot.renderer.BoundsStylePanel;
import org.autoplot.renderer.InternalStylePanel;
import org.xml.sax.SAXException;

/**
 * Extra methods to support AutoplotUI.
 * @author jbf
 */
public class GuiSupport {

    private static final Logger logger= org.das2.util.LoggerManager.getLogger("autoplot.guisupport");

    AutoplotUI parent;

    public GuiSupport(AutoplotUI parent) {
        this.parent = parent;
    }

    public void doPasteDataSetURL() {
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        Transferable contents = clipboard.getContents(null);
        if ( contents==null ) {
            logger.fine("contents was null");
            return;
        }
        boolean hasTransferableText =
                contents.isDataFlavorSupported(DataFlavor.stringFlavor);
        String result = null;
        if (hasTransferableText) {
            try {
                result = (String) contents.getTransferData(DataFlavor.stringFlavor);
            } catch (UnsupportedFlavorException | IOException ex) {
                //highly unlikely since we are using a standard DataFlavor
                logger.log( Level.WARNING, ex.getMessage(), ex );
            }
        }
        if (result != null) {
            parent.dataSetSelector.setValue(result);
        }
    }

    public void doCopyDataSetURL() {
        StringSelection stringSelection = new StringSelection( DataSetURI.toUri(parent.dataSetSelector.getValue()).toString() );
        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
        clipboard.setContents(stringSelection, new ClipboardOwner() {
            @Override
            public void lostOwnership(Clipboard clipboard, Transferable contents) {
            }
        });
    }

    public void doCopyDataSetImage() {
        Runnable run = new Runnable() {
            @Override
            public void run() {
                ImageSelection imageSelection = new ImageSelection();
                DasCanvas c = parent.applicationModel.canvas;
                Image i = c.getImage(c.getWidth(), c.getHeight());
                imageSelection.setImage(i);
                Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
                clipboard.setContents(imageSelection, new ClipboardOwner() {
                    @Override
                    public void lostOwnership(Clipboard clipboard, Transferable contents) {
                    }
                });
            }
        };
        new Thread(run, "CopyDataSetToClipboardThread").start();
    }

    /**
     * attempt to get the Frame for the component, which may already be a Frame.
     * @param parent
     * @return
     */
    public static Frame getFrameForComponent( Component parent ) { 
        if ( !( parent instanceof Frame ) ) {
            parent= SwingUtilities.getWindowAncestor(parent);
        }
        if ( parent instanceof Frame ) {
            return (Frame)parent;
        } else {
            return null;
        }
    }

    /**
     * return a GUI controller for the RenderType.
     * @param renderType the render type, such as RenderType.colorScatter
     * @return the GUI controller.
     */
    public static PlotStylePanel.StylePanel getStylePanel( RenderType renderType ) { 
        PlotStylePanel.StylePanel editorPanel;
        if ( null == renderType ) {
            //TODO: consider generic style panel that is based on completions of Renderer control.
            editorPanel= new SeriesStylePanel( );
            return editorPanel;
        } 
        switch (renderType) {
            case spectrogram:
            case nnSpectrogram:
                editorPanel= new SpectrogramStylePanel( );
                break;
            case pitchAngleDistribution:
                editorPanel= new PitchAngleDistributionStylePanel( );
                break;
            case polar:
                editorPanel= new ColorScatterStylePanel( );
                break;
            case hugeScatter:
                editorPanel= new HugeScatterStylePanel( );
                break;
            case colorScatter:
                editorPanel= new ColorScatterStylePanel( );
                break;
            case contour:
                editorPanel= new ContourStylePanel( );
                break;
            case internal:
                editorPanel= new InternalStylePanel( );
                break;
            case bounds:
                editorPanel= new BoundsStylePanel( );
                break;
            case digital:
                editorPanel= new DigitalStylePanel( );
                break;
            case orbitPlot:
                editorPanel= new OrbitStylePanel( );
                break;
            case eventsBar:
                editorPanel= new EventsStylePanel( );
                break;
            case stackedHistogram:
                editorPanel= new StackedHistogramStylePanel( );
                break;
            case image:
                editorPanel= new ImageStylePanel( );
                break;
            default:
                //TODO: consider generic style panel that is based on completions of Renderer control.
                editorPanel= new SeriesStylePanel( );
                break;
        }
        return editorPanel;
    }
    
    public static void editPlotElement( ApplicationModel applicationModel, Component parent ) {
        
        Application dom = applicationModel.dom;

        AddPlotElementDialog dia = new AddPlotElementDialog( getFrameForComponent(parent), true);
        dia.getPrimaryDataSetSelector().setTimeRange(dom.getTimeRange());
        dia.getSecondaryDataSetSelector().setTimeRange(dom.getTimeRange());
        dia.getTertiaryDataSetSelector().setTimeRange(dom.getTimeRange());

        String suri= dom.getController().getFocusUri();

        setAddPlotElementUris( applicationModel, dom, dia, suri );

        dia.setTitle( "Editing Plot Element" ); 
        
        WindowManager.getInstance().showModalDialog( dia );
        
        if (dia.isCancelled()) {
            return;
        }
        handleAddElementDialog(dia, dom, applicationModel);

    }

    private static void setAddPlotElementUris( ApplicationModel applicationModel,
            Application dom, AddPlotElementDialog dia, String suri ) {

        Pattern hasKidsPattern= Pattern.compile("vap\\+internal\\:(data_\\d+)(,(data_\\d+))?+(,(data_\\d+))?+");
        Matcher m= hasKidsPattern.matcher(suri);

        dia.getPrimaryDataSetSelector().setRecent(AutoplotUtil.getUrls(applicationModel.getRecent()));
        dia.getSecondaryDataSetSelector().setRecent(AutoplotUtil.getUrls(applicationModel.getRecent()));
        dia.getTertiaryDataSetSelector().setRecent(AutoplotUtil.getUrls(applicationModel.getRecent()));

        if ( m.matches() ) {
            int depCount= m.group(5)!=null ? 2 : ( m.group(3)!=null ? 1 : ( m.group(1)!=null ? 0 : -1 ) );
            dia.setDepCount(depCount);
            int[] groups;
            DataSetSelector[] selectors;
            selectors= new DataSetSelector[] { dia.getPrimaryDataSetSelector(),
                dia.getSecondaryDataSetSelector(),
                dia.getTertiaryDataSetSelector(), };
            switch (depCount) {
                case 2:
                    groups= new int[] { 5,1,3 };
                    break;
                case 1:
                    groups= new int[] { 3,1 };
                    break;
                default:
                    groups= new int[] { 1 };
                    break;
            }
            for ( int i=0; i<groups.length; i++ ) {
                DataSourceFilter dsf= (DataSourceFilter) DomUtil.getElementById( dom, m.group(groups[i]) );
                if ( dsf==null ) {
                    selectors[i].setValue( m.group(groups[i]) );
                } else if ( dsf.getUri().length()==0 ) {
                    selectors[i].setValue( m.group(groups[i]) ); //TODO: interesting branch that I hit on a telecon with Reiner.
                } else if ( dsf.getUri().startsWith("vap+internal:")) {
                    selectors[i].setValue( m.group(groups[i]) ); //TODO: does this work, multiple levels?
                } else {
                    selectors[i].setValue(dsf.getUri());
                    dia.setFilter(i,dsf.getFilters());                               
                }
            }
        } else {
            dia.getPrimaryDataSetSelector().setValue( suri );
        }
    }


    /**
     * enter dialog to possibly add a plot to the vap, or combine multiple 
     * URIs combined into an internal dataset.
     * @param title title for the popup.
     */
    void addPlotElement( String title ) {

        ApplicationModel applicationModel = parent.applicationModel;
        DataSetSelector dataSetSelector = parent.dataSetSelector;
        Application dom = applicationModel.dom;

        AddPlotElementDialog dia = new AddPlotElementDialog( parent, true);
        dia.getPrimaryDataSetSelector().setTimeRange(dom.getTimeRange());
        dia.getSecondaryDataSetSelector().setTimeRange(dom.getTimeRange());
        dia.getTertiaryDataSetSelector().setTimeRange(dom.getTimeRange());

        String val= dataSetSelector.getValue();
        if ( val.startsWith("vap+internal:") ) {
            setAddPlotElementUris( applicationModel, dom, dia, val );
        } else {
            dia.getPrimaryDataSetSelector().setValue(val);
            dia.getSecondaryDataSetSelector().setValue(val);
            dia.getTertiaryDataSetSelector().setValue(val);
            dia.getPrimaryDataSetSelector().setRecent(AutoplotUtil.getUrls(applicationModel.getRecent()));
            dia.getSecondaryDataSetSelector().setRecent(AutoplotUtil.getUrls(applicationModel.getRecent()));
            dia.getTertiaryDataSetSelector().setRecent(AutoplotUtil.getUrls(applicationModel.getRecent()));
        }

        if ( title==null ) {
            title= "Adding Plot Element";
        }
        dia.setTitle( title );
        
        WindowManager.getInstance().showModalDialog(dia);

        if (dia.isCancelled()) {
            return;
        }
        handleAddElementDialog(dia, dom, applicationModel);

    }
    
    /**
     * same as addPlotElement, but a future version may allow the timerange to be set, combining things into one
     * GUI.
     * @param title title for the popup.
     * @param furi the URI.
     */
    void addPlotElementFromBookmark( String title, String furi ) {
        DataSourceFactory factory=null;
        try {
            factory = DataSetURI.getDataSourceFactory( DataSetURI.getURI(furi), new NullProgressMonitor() );
        } catch (IOException | IllegalArgumentException | URISyntaxException ex) {
            logger.log(Level.SEVERE, null, ex);
        }
        assert factory!=null; // we checked this earlier.
        TimeSeriesBrowse tsb= factory.getCapability( TimeSeriesBrowse.class );
        DatumRange uriRange= null;
        if ( tsb!=null ) {
            try {
                tsb.setURI(furi);
                uriRange= tsb.getTimeRange();
            } catch (ParseException ex) {
                Logger.getLogger(AutoplotUI.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        Application dom= parent.getDom();
        String uri= furi;
        DatumRange dr;
        if ( !(dom.getTimeRange()==Application.DEFAULT_TIME_RANGE) && !dom.getTimeRange().equals( uriRange ) ) {
            
            dr= DataSetSelector.pickTimeRange( parent, 
                Arrays.asList( dom.getTimeRange(), uriRange ),
                Arrays.asList( "Current", "URI" )
                );
            if ( dr!=uriRange ) {
                try {
                    uri= DataSourceUtil.setTimeRange(uri,dom.getTimeRange(),new NullProgressMonitor());
                } catch (URISyntaxException | IOException | ParseException ex) {
                    Logger.getLogger(AutoplotUI.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
        ApplicationModel applicationModel = parent.applicationModel;

        AddPlotElementDialog dia = new AddPlotElementDialog( parent, true);
        dia.getPrimaryDataSetSelector().setTimeRange(dom.getTimeRange());
        dia.getSecondaryDataSetSelector().setTimeRange(dom.getTimeRange());
        dia.getTertiaryDataSetSelector().setTimeRange(dom.getTimeRange());

        String val= uri;
        if ( val.startsWith("vap+internal:") ) {
            setAddPlotElementUris( applicationModel, dom, dia, val );
        } else {
            dia.getPrimaryDataSetSelector().setValue(val);
            dia.getSecondaryDataSetSelector().setValue(val);
            dia.getTertiaryDataSetSelector().setValue(val);
            dia.getPrimaryDataSetSelector().setRecent(AutoplotUtil.getUrls(applicationModel.getRecent()));
            dia.getSecondaryDataSetSelector().setRecent(AutoplotUtil.getUrls(applicationModel.getRecent()));
            dia.getTertiaryDataSetSelector().setRecent(AutoplotUtil.getUrls(applicationModel.getRecent()));
        }

        if ( title==null ) {
            title= "Adding Plot Element";
        }
        dia.setTitle( title );
        
        WindowManager.getInstance().showModalDialog(dia);

        if (dia.isCancelled()) {
            return;
        }
        handleAddElementDialog(dia, dom, applicationModel);

    }    

    /**
     * dump the data using the DataSourceFormat object.
     * @param fds the dataset in its original form.
     * @param dsf the data source filter.
     * @param pe the plot element.
     * @param format format object.
     * @param uriOut output location, must be a local file.
     * @param dscontrol "plotElementTrim": load the data and trim to xaxis settings. "plotElement" data processed to make visible.
     * @throws IOException 
     */
    private void doDumpData( QDataSet fds, DataSourceFilter dsf, PlotElement pe, DataSourceFormat format, String uriOut, String dscontrol  ) throws IOException {

        logger.log(Level.FINE, "exporting data to {0} using format {1}", new Object[]{uriOut, format});

        ProgressMonitor mon=null;
        try {
            QDataSet ds= fds;

            if ( dsf.getController().getTsb()!=null ) {
                DataSource dss= dsf.getController().getDataSource();
                if ( dss==null ) {
                    logger.fine("looks like a TSB is used, but the data is not a time series, don't reload");
                } else {
                    dsf.getController().getTsb().setTimeResolution(null);
                    mon= DasProgressPanel.createFramed(parent, "reloading data at native resolution");
                    ds= dss.getDataSet( mon );
                    if ( mon.isCancelled() ) {
                        parent.setStatus( "export data cancelled" );
                        return;
                    }
                    if ( !mon.isFinished() ) mon.finished(); // in cause the getDataSet method fails to call finished.
                }
            }

            mon= DasProgressPanel.createFramed( parent, "formatting data" );
            switch (dscontrol) {
                case "plotElementTrim":
                    {
                        DasPlot p= pe.getController().getDasPlot();
                        DatumRange xbounds= p.getXAxis().getDatumRange();
                        QDataSet dsout=  pe.getController().getDataSet();
                        //dsout= DataSetOps.processDataSet( pe.getComponent(), dsout, DasProgressPanel.createFramed(parent, "process TSB timeseries at native resolution") );
                        long t0= System.currentTimeMillis();
                        if ( SemanticOps.isRank2Waveform(dsout) ) {
                            dsout= DataSetOps.flattenWaveform(dsout);
                            //dsout= ArrayDataSet.copy( dsout );
                        }       dsout= SemanticOps.trim( dsout, xbounds, null );
                        format.formatData( uriOut, dsout, mon );
                        logger.log( Level.FINE, "format in {0} millis", (System.currentTimeMillis()-t0));
                        break;
                    }
                case "plotElement":
                    {
                        long t0= System.currentTimeMillis();
                        QDataSet dsout=  pe.getController().getDataSet();
                        format.formatData( uriOut, dsout, mon );
                        logger.log( Level.FINE, "format in {0} millis", (System.currentTimeMillis()-t0));
                        break;
                    }
                default:
                    {
                        long t0= System.currentTimeMillis();
                        format.formatData( uriOut, ds, mon );
                        logger.log( Level.FINE, "format in {0} millis", (System.currentTimeMillis()-t0));
                        break;
                    }
            }
            parent.setStatus("Wrote " + org.autoplot.datasource.DataSourceUtil.unescape(uriOut) );
        } catch ( IllegalArgumentException ex ) {
            parent.applicationModel.getExceptionHandler().handle(ex);
            logger.log(Level.FINE, " ..caused exception: {0} using format {1}", new Object[]{uriOut, format});
            logger.log(Level.SEVERE, "exception "+uriOut, ex );
        } catch (RuntimeException ex ) {
            parent.applicationModel.getExceptionHandler().handleUncaught(ex);
            logger.log(Level.FINE, " ..caused exception: {0} using format {1}", new Object[]{uriOut, format});
            logger.log(Level.SEVERE,  "exception "+uriOut, ex );
        } catch (Exception ex) {
            parent.applicationModel.getExceptionHandler().handle(ex);
            logger.log(Level.FINE, " ..caused exception: {0} using format {1}", new Object[]{uriOut, format});
            logger.log(Level.SEVERE,  "exception "+uriOut, ex );
        }
        if ( mon!=null && !mon.isFinished() ) mon.finished(); // in case they forgot the tidy up.
    }

    /**
     * provide action to allow users to export a dataset to formats that support this.
     * @param dom
     * @return 
     */
    Action getDumpDataAction( final Application dom ) {
        return new AbstractAction("Export Data...") {
            @Override
            public void actionPerformed( ActionEvent e ) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                final ExportDataPanel edp= new ExportDataPanel();
                edp.setDataSet(dom);

                final PlotElement pe= dom.getController().getPlotElement();
                final DataSourceFilter dsf= dom.getController().getDataSourceFilterFor( pe );
                QDataSet ds= dsf.getController().getDataSet();

                if (ds == null) {
                    JOptionPane.showMessageDialog(parent, "No Data to Export.");
                    return;
                }
//TODO: check extension.
                List<String> exts = DataSourceRegistry.getInstance().getFormatterExtensions();
                Collections.sort(exts);
                edp.getFormatDL().setModel( new DefaultComboBoxModel(exts.toArray()) );
                edp.getFormatDL().setRenderer( new DefaultListCellRenderer() {
                    @Override
                    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
                        String ext= String.valueOf(value);
                        DataSourceFormat format= DataSourceRegistry.getInstance().getFormatByExt(ext);
                        Component parent= super.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus );
                        if ( parent instanceof JLabel ) {
                            if ( format!=null ) {
                                ((JLabel)parent).setText( value.toString() + " " + format.getDescription() );
                            }
                        }
                        return parent;
                    }
                });
                Preferences prefs= AutoplotSettings.settings().getPreferences(AutoplotUI.class);
                String currentFileString = prefs.get("ExportDataCurrentFile", "");
                String currentExtString = prefs.get("ExportDataCurrentExt", ".txt");
                if ( !currentExtString.equals("") ) {
                    edp.getFormatDL().setSelectedItem(currentExtString);
                }
                if ( !currentFileString.equals("") ) {
                    URISplit split= URISplit.parse(currentFileString);
                    edp.getFilenameTF().setText(split.file);
                    edp.getFormatDL().setSelectedItem( "." + split.ext );
                    if ( currentFileString.contains("/") && ( currentFileString.startsWith("file:") || currentFileString.startsWith("/") ) ) {
                        edp.setFile( currentFileString );
                        if ( split.params!=null && edp.getDataSourceFormatEditorPanel()!=null ) {
                            edp.getDataSourceFormatEditorPanel().setURI(currentFileString);
                        }
                    }
                }

                if ( dsf.getController().getTsb()!=null ) {
                    edp.setTsb(true);
                }
                
                if ( AutoplotUtil.showConfirmDialog2( parent, edp, "Export Data", JOptionPane.OK_CANCEL_OPTION )==JOptionPane.OK_OPTION ) {
                     try {
                        String name= edp.getFilenameTF().getText();
                        String ext = edp.getExtension();
                        String file;
                        try {
                            file= edp.getFilename();
                        } catch ( IllegalArgumentException ex ) {
                            JOptionPane.showMessageDialog(parent, ex.getMessage() );
                            return;
                        }

                        if (ext == null) {
                            ext = "";
                        }

                        URISplit split= URISplit.parse(file);
                        
                        final DataSourceFormat format = DataSourceRegistry.getInstance().getFormatByExt(ext); //OKAY
                        if (format == null) {
                            JOptionPane.showMessageDialog(parent, "No formatter for extension: " + ext);
                            return;
                        }
                        
                        String s= URISplit.format(split);

                        // this can also support aggregations.
                        final DataSourceFormat formata= DataSetURI.getDataSourceFormat( new URI(s) );
                        
                        DataSourceFormatEditorPanel opts= edp.getDataSourceFormatEditorPanel();
                        if ( opts!=null ) {
//                            try {
//                                opts.getURI();
//                            } catch ( NullPointerException ex ) {
//                                System.err.println("here bug 1975");
//                            }
                            URISplit splitopts= URISplit.parse(opts.getURI());
                            if ( splitopts.params!=null && splitopts.params.length()==0 ) {
                                splitopts.params= null;
                            }
                            URISplit splits= URISplit.parse(s);
                            splitopts.file= splits.file;
                            s= URISplit.format(splitopts); //TODO: this probably needs a lookin at.
                            name= DataSourceUtil.unescape(s);
                        }

                        prefs.put("ExportDataCurrentFile", name );
                        prefs.put("ExportDataCurrentExt", ext );

                        final QDataSet fds= ds;
                        final String uriOut= s;

                        final String formatControl;
                        if ( edp.isFormatPlotElement() ) {
                            formatControl= "plotElement";
                        } else if ( edp.isFormatPlotElementAndTrim() ) {
                            formatControl= "plotElementTrim";
                        } else if ( edp.isOriginalData() ) {
                            formatControl= "dataSourceFilter";
                        } else {
                            JOptionPane.showMessageDialog(parent, "Selected data cannot be exported to this format " + ext );
                            return;
                        }
                        
                        Runnable run= new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    doDumpData( fds,dsf,pe,formata,uriOut,formatControl );
                                } catch ( IOException ex ) {
                                    parent.applicationModel.getExceptionHandler().handle(ex);
                                }
                            }
                        };

                        new Thread( run ).start();

                    } catch ( IllegalArgumentException | HeadlessException | URISyntaxException ex) {
                        parent.applicationModel.getExceptionHandler().handle(ex);

                    } catch (RuntimeException ex ) {
                        parent.applicationModel.getExceptionHandler().handleUncaught(ex);
                    }

                }
            }
        };
    }

    /**
     * provide action to allow users to export a dataset to formats that support this.
     * @param dom
     * @return 
     */
    Action getDumpAllDataAction( final Application dom ) {
        return new AbstractAction("Export All Data...") {
            @Override
            public void actionPerformed( ActionEvent e ) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                final ExportDataBundle edw= new ExportDataBundle();

                ArrayList<String> uris= new ArrayList<>();
                for ( DataSourceFilter dsf: dom.getDataSourceFilters() ) {
                    uris.add(dsf.getUri());
                }
                
                edw.setUris( uris.toArray(new String[uris.size()]) );
                
                if ( JOptionPane.showConfirmDialog( parent, edw, "export all", JOptionPane.OK_CANCEL_OPTION )==JOptionPane.OK_OPTION ) {
                    try {
                        ScriptContext.formatDataSet( edw.getDataSet(), edw.getUri() );
                        parent.setStatus("Wrote " + org.autoplot.datasource.DataSourceUtil.unescape(edw.getUri()) );
                    } catch (Exception ex) {
                        logger.log(Level.SEVERE, null, ex);
                    }
                }
            }
        };
    }
    
    
    public Action createNewDOMAction() {
        return new AbstractAction("Reset Window...") {
            @Override
            public void actionPerformed( ActionEvent e ) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                if ( parent.stateSupport.isDirty() ) {
                    String msg= "The application has been modified.  Do you want to save your changes?";
                    int result= JOptionPane.showConfirmDialog( parent, msg, "Application Modified", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE );
                    if ( result==JOptionPane.OK_OPTION ) {
                        result= parent.stateSupport.saveAs();
                        if ( result==JFileChooser.CANCEL_OPTION ) {
                            return;
                        }
                    } else if ( result==JOptionPane.CANCEL_OPTION || result==JOptionPane.CLOSED_OPTION ) {
                        return;
                    }
                }
                Runnable run= new Runnable() {
                    @Override
                    public void run() {
                        parent.dom.getController().reset();
                        parent.undoRedoSupport.resetHistory();
                        parent.applicationModel.setVapFile(null);
                        parent.stateSupport.close();
                        parent.tickleTimer.tickle();
                    }
                };

                // https://sourceforge.net/tracker/?func=detail&aid=3557440&group_id=199733&atid=970682
                new Thread(run).start(); // allow reset when all the request processor threads are full.  TODO: I'm not sure why this appeared to be the case.
                //RequestProcessor.invokeLater(run);
            }
        };
    }

    /**
     * create a new AutoplotUI
     * @return the new ApplicationModel
     */
    ApplicationModel newApplication() {
        final ApplicationModel model = new ApplicationModel();
        model.setExceptionHandler( GuiSupport.this.parent.applicationModel.getExceptionHandler() );
        Runnable run= new Runnable() {
            @Override
            public void run() {
                model.addDasPeersToApp();
                model.dom.getOptions().setDataVisible( parent.applicationModel.dom.getOptions().isDataVisible() ); // options has funny sync code and these must be set before AutoplotUI is constructed.
                model.dom.getOptions().setLayoutVisible( parent.applicationModel.dom.getOptions().isLayoutVisible() );                
                AutoplotUI view = new AutoplotUI(model);
                view.setLocationRelativeTo(GuiSupport.this.parent);
                view.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
                java.awt.Point p= view.getLocation();
                p.translate( 20,20 );
                view.setLocation( p );
                view.setVisible(true);
                OptionsPrefsController opc= new OptionsPrefsController( model.dom.getOptions() );
                opc.loadPreferencesWithEvents();
                view.setMessage("ready");
                AutoplotUI.checkStatusLoop(view);
            }
        };
        try {
            if ( SwingUtilities.isEventDispatchThread() ) {
                run.run();
            } else {
                SwingUtilities.invokeAndWait(run);
            }
        } catch ( InterruptedException | InvocationTargetException ex ) {
            throw new RuntimeException(ex);
        }
        return model;
    }

    /**
     * clone the application into a new AutoplotUI
     * @return
     */
    ApplicationModel cloneApplication() {
        final ApplicationModel model = new ApplicationModel();
        model.setExceptionHandler( GuiSupport.this.parent.applicationModel.getExceptionHandler() );
        Runnable run= new Runnable() {
            @Override
            public void run() {
                model.addDasPeersToApp();
                model.dom.getOptions().setDataVisible( parent.applicationModel.dom.getOptions().isDataVisible() ); // options has funny sync code and these must be set before AutoplotUI is constructed.
                model.dom.getOptions().setLayoutVisible( parent.applicationModel.dom.getOptions().isLayoutVisible() );
                AutoplotUI view = new AutoplotUI(model);
                view.setLocationRelativeTo(GuiSupport.this.parent);
                view.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
                java.awt.Point p= view.getLocation();
                p.translate( 20,20 );
                view.setLocation( p );
                view.setVisible(true);
                OptionsPrefsController opc= new OptionsPrefsController( model.dom.getOptions() );
                opc.loadPreferencesWithEvents();
                view.setMessage("ready");
                AutoplotUI.checkStatusLoop(view);
                Canvas size= parent.applicationModel.dom.getCanvases(0);
                view.resizeForCanvasSize( size.getWidth(), size.getHeight() );
            }
        };
        try {
            if ( SwingUtilities.isEventDispatchThread() ) {
                run.run();
            } else {
                SwingUtilities.invokeAndWait(run);
            }
        } catch ( InterruptedException | InvocationTargetException ex ) {
            throw new RuntimeException(ex);
        }
        model.dom.syncTo( parent.applicationModel.dom );
        return model;
    }
    
    public Action createNewApplicationAction() {
        return new AbstractAction("New Window") {
            @Override
            public void actionPerformed( ActionEvent e ) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                newApplication();
            }
        };
    }

    public Action createCloneApplicationAction() {
        return new AbstractAction("Clone to New Window") {
            @Override
            public void actionPerformed( ActionEvent e ) {
                org.das2.util.LoggerManager.logGuiEvent(e);        
                cloneApplication();
            }
        };
    }

    private static Map<String,RenderType> getRenderTypeForString( ) {
        Map<String,RenderType> tt= new LinkedHashMap();
        tt.put( "Scatter", RenderType.scatter );
        tt.put( "Color Scatter", RenderType.colorScatter );
        tt.put( "Series", RenderType.series );
        tt.put( "Stair Steps", RenderType.stairSteps );
        tt.put( "Fill To Zero", RenderType.fillToZero );
        tt.put( "Huge Scatter", RenderType.hugeScatter );
        tt.put( "Spectrogram", RenderType.spectrogram );
        tt.put( "Nearest Neighbor Spectrogram", RenderType.nnSpectrogram);
        tt.put( "Digital", RenderType.digital);
        tt.put( "Events Bar", RenderType.eventsBar);
        tt.put( "Image", RenderType.image);
        tt.put( "Pitch Angle Distribution", RenderType.pitchAngleDistribution);
        tt.put( "Orbit Plot", RenderType.orbitPlot );
        tt.put( "Contour Plot", RenderType.contour);
        tt.put( "Stacked Histogram", RenderType.stackedHistogram);
        return tt;
    }

    public static JMenu createEZAccessMenu(final Plot plot) {

        JMenu result = new JMenu("Plot Style");
        JMenuItem mi;

        result.setName(plot.getId()+"_ezaccessmenu");
        
        Map<String,RenderType> tt= getRenderTypeForString();

        //tt.put( "Contour Plot", RenderType.contour );  //this has issues, hide for now.
        //tt.remove( "Pitch Angle Distribution" ); // this requires a specific scheme of data, hide for now (rte_1765139930_20130112_134531)

        for ( Entry<String,RenderType> ee: tt.entrySet() ) {
            final Entry<String,RenderType> fee= ee;
            mi= new JCheckBoxMenuItem(new AbstractAction(fee.getKey()) {
                @Override
                public void actionPerformed(ActionEvent e) {
                    org.das2.util.LoggerManager.logGuiEvent(e);
                    PlotElement pe = plot.getController().getApplication().getController().getPlotElement();
                    pe.setRenderType(fee.getValue());
                }
            });
            result.add(mi);
            //group.add(mi);
        }
  
        return result;
    }

    protected void addKeyBindings(JPanel thisPanel) {
        thisPanel.getActionMap().put("UNDO", parent.undoRedoSupport.getUndoAction());
        thisPanel.getActionMap().put("REDO", parent.undoRedoSupport.getRedoAction());
        thisPanel.getActionMap().put("RESET_ZOOM", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                parent.applicationModel.resetZoom();
            }
        });
        thisPanel.getActionMap().put("INCREASE_FONT_SIZE", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                parent.applicationModel.increaseFontSize();
            }
        });
        thisPanel.getActionMap().put("DECREASE_FONT_SIZE", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                parent.applicationModel.decreaseFontSize();
            }
        });

        thisPanel.getActionMap().put("NEXT_PLOT_ELEMENT", new AbstractAction() {
            @Override
            public void actionPerformed( ActionEvent e ) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                Application dom= parent.dom;
                PlotElement p= dom.getController().getPlotElement();
                int idx= Arrays.asList( dom.getPlotElements() ).indexOf(p);
                if ( idx==-1 ) idx=0;
                idx++;
                if ( idx==dom.getPlotElements().length ) idx=0;
                dom.getController().setPlotElement( dom.getPlotElements(idx) );
            }
        });

        thisPanel.getActionMap().put("PREV_PLOT_ELEMENT", new AbstractAction() {
            @Override
            public void actionPerformed( ActionEvent e ) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                Application dom= parent.dom;
                PlotElement p= dom.getController().getPlotElement();
                int idx= Arrays.asList( dom.getPlotElements() ).indexOf(p);
                if ( idx==-1 ) idx=0;
                idx--;
                if ( idx==-1 ) idx= dom.getPlotElements().length-1;
                dom.getController().setPlotElement( dom.getPlotElements(idx) );
            }
        });

        thisPanel.getActionMap().put("SAVE", new AbstractAction() {
            @Override
            public void actionPerformed( ActionEvent e ) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                parent.stateSupport.createSaveAction().actionPerformed(e);
            }
        });
        thisPanel.getActionMap().put("RELOAD_ALL", new AbstractAction() {
            @Override
            public void actionPerformed( ActionEvent e ) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                RequestProcessor.invokeLater( new Runnable() { 
                    @Override
                    public void run() {
                        AutoplotUtil.reloadAll(parent.dom);
                    }
                } );
            }
        });

        InputMap map = new ComponentInputMap(thisPanel);

        Toolkit tk= Toolkit.getDefaultToolkit();
        map.put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, tk.getMenuShortcutKeyMask() ), "UNDO");
        map.put(KeyStroke.getKeyStroke(KeyEvent.VK_Y, tk.getMenuShortcutKeyMask() ), "REDO");
        map.put(KeyStroke.getKeyStroke(KeyEvent.VK_R, tk.getMenuShortcutKeyMask() ), "RESET_ZOOM");
        map.put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, tk.getMenuShortcutKeyMask()), "DECREASE_FONT_SIZE");
        map.put(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, tk.getMenuShortcutKeyMask()), "INCREASE_FONT_SIZE");
        map.put(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, tk.getMenuShortcutKeyMask()), "INCREASE_FONT_SIZE");
        map.put(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, KeyEvent.SHIFT_DOWN_MASK | tk.getMenuShortcutKeyMask()), "INCREASE_FONT_SIZE");  // american keyboard
        map.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, KeyEvent.CTRL_DOWN_MASK), "NEXT_PLOT_ELEMENT");
        map.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, KeyEvent.SHIFT_DOWN_MASK | KeyEvent.CTRL_DOWN_MASK), "PREV_PLOT_ELEMENT");
        map.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, tk.getMenuShortcutKeyMask() ), "SAVE");
        map.put(KeyStroke.getKeyStroke(KeyEvent.VK_L, tk.getMenuShortcutKeyMask() ), "RELOAD_ALL");
        thisPanel.setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW, map);

    }

    protected void exportRecent(Component c) {
        JFileChooser chooser = new JFileChooser();
        chooser.setFileFilter(new javax.swing.filechooser.FileFilter() {
            @Override
            public boolean accept(File f) {
                String s= f.toString();
                if ( s==null ) return false; // Findbugs OK: There was a Windows bug where file.toString could return null.
                return f.isDirectory() || s.endsWith(".xml");
            }
            @Override
            public String getDescription() {
                return "bookmarks files (*.xml)";
            }
        });
        int r = chooser.showSaveDialog(c);
        if (r == JFileChooser.APPROVE_OPTION) {
            try {
                File f = chooser.getSelectedFile();
                if (!f.toString().endsWith(".xml")) {
                    f = new File(f.toString() + ".xml");
                }
                try (FileOutputStream out = new FileOutputStream(f)) {
                    Bookmark.formatBooks(out,parent.applicationModel.getRecent());
                }
            } catch (IOException e) {
                logger.log( Level.WARNING, e.getMessage(), e );
            }
        }
    }

    private static FileFilter getFileNameExtensionFilter(final String description, final String ext) {
        return new FileFilter() {
            @Override
            public boolean accept(File f) {
                String s= f.toString();
                if ( s==null ) return false;// Findbugs OK: There was a Windows bug where file.toString could return null.
                return f.isDirectory() || s.endsWith(ext);
            }
            @Override
            public String getDescription() {
                return description;
            }
        };
    }

    private static File currentFile;

    public static Action getPrintAction( final Application app, final Component parent,final String ext) {
        return new AbstractAction("Print as "+ext.toUpperCase()) {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);                
                final JPanel decor;
                final DasCanvas canvas = app.getController().getDasCanvas();
                final JFileChooser fileChooser = new JFileChooser();
                fileChooser.setDialogTitle("Print to "+ext.toUpperCase());
                fileChooser.setFileFilter(getFileNameExtensionFilter( ext + " files", ext ));
                Preferences prefs = AutoplotSettings.settings().getPreferences(DasCanvas.class);
                String savedir = prefs.get("savedir", null);
                if (savedir != null) fileChooser.setCurrentDirectory(new File(savedir));
                if (currentFile != null) {
                    if ( currentFile.toString().endsWith("."+ext) ) {
                        fileChooser.setSelectedFile(currentFile);
                    } else {
                        fileChooser.setSelectedFile(new File( currentFile.toString()+"."+ext) );
                    }
                }
                if ( ext.equals("pdf") ) {
                    decor= new PdfOptionsPanel();
                    fileChooser.setAccessory(decor);
                } else {
                    decor= null;
                }
                int choice = fileChooser.showSaveDialog(parent);
                if (choice == JFileChooser.APPROVE_OPTION) {

                    String fname = fileChooser.getSelectedFile().toString();
                    if (!fname.toLowerCase().endsWith("."+ext)) fname += "."+ext;
                    final String ffname = fname;
                    prefs.put("savedir", new File(ffname).getParent());
                    currentFile = new File(ffname.substring(0, ffname.length() - 4));
                    Runnable run = new Runnable() {
                        @Override
                        public void run() {
                            try {
                                switch (ext) {
                                    case "png":
                                        canvas.writeToPng(ffname);
                                        break;
                                    case "pdf":
                                        try ( FileOutputStream out = new FileOutputStream(ffname) ){
                                            
                                            PdfGraphicsOutput go = new PdfGraphicsOutput();

                                            PdfOptionsPanel pdecor= (PdfOptionsPanel)decor; // findbugs OKAY
                                            go.setGraphicsShapes( pdecor.fontsAsShapesCB.isSelected() );
                                            go.setOutputStream(out);
                                            if ( pdecor.manualWidthCB.isSelected() ) {
                                                double mant= Double.parseDouble(pdecor.widthTF.getText()); //TODO>: FormattedTextField
                                                String units= (String)pdecor.unitsComboBox.getSelectedItem();
                                                switch (units) {
                                                    case "inches":
                                                        mant= mant * 72;
                                                        break;
                                                    case "centimeters":
                                                        mant= mant * 72 / 2.54;
                                                        break;
                                                    default:
                                                        throw new IllegalArgumentException("implementation error: "+units);
                                                }
                                                // mant is the number of pixels width.
                                                
                                                int ppi= (int)( canvas.getWidth() * 72 / mant );
                                                go.setPixelsPerInch( ppi );
                                                go.setSize( canvas.getWidth(), canvas.getHeight() );
                                            } else if ( pdecor.getPixelsPerInch().length()>0 ) {
                                                int ppi= Integer.parseInt(pdecor.getPixelsPerInch());
                                                go.setPixelsPerInch(ppi);
                                                go.setSize( canvas.getWidth(), canvas.getHeight() );
                                            } else {
                                                go.setSize( canvas.getWidth(), canvas.getHeight() );
                                            }
                                            go.start();
                                            canvas.print(go.getGraphics());
                                            go.finish();
                                        }   
                                        break;
                                    case "svg":
                                        canvas.writeToSVG(ffname);
                                        break;
                                    default:
                                        throw new IllegalArgumentException("implementation error: "+ext);
                                }
                                app.getController().setStatus("wrote to " + ffname);
                            } catch (java.io.IOException ioe) {
                                DasApplication.getDefaultApplication().getExceptionHandler().handle(ioe);
                            }
                        }
                    };
                    new Thread(run, "writePrint").start();
                }
            }
        };
    }

    /**
     * allow user to pick out data from a vap file.
     * @param dom
     * @param plot
     * @param pelement
     * @param vap
     */
    private static void mergeVap( Application dom, Plot plot, PlotElement pelement, String vap ) {
        try {
            ImportVapDialog d = new ImportVapDialog();
            if ( vap.contains("?") ) {
                int i= vap.indexOf('?');
                vap= vap.substring(0,i);
            }
            d.setVap(vap);
            if ( d.showDialog( SwingUtilities.getWindowAncestor( dom.getController().getDasCanvas() ) )==JOptionPane.OK_OPTION ) {
                String lock= "merging vaps";
                dom.getController().registerPendingChange( d,lock );
                dom.getController().performingChange( d, lock );
                List<String> uris= d.getSelectedURIs();
                for ( String uri: uris ) {
                    dom.getController().doplot( plot, pelement, uri );
                    pelement= null; //otherwise we'd clobber last dataset.
                }
                dom.getController().changePerformed( d, lock );
            }
        } catch (IOException ex) {
            logger.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }

    /**
     * Maybe import the bookmarks in response to the "bookmarks:..." URI.
     * @param bookmarksFile URL which refers to a local, HTTP, HTTPS, or FTP resource.
     */
    public void importBookmarks( String bookmarksFile )  {

        ImportBookmarksGui gui= new ImportBookmarksGui();
        gui.getBookmarksFilename().setText(bookmarksFile+" ?");
        gui.getRemote().setSelected(true);
        int r = JOptionPane.showConfirmDialog( parent, gui, "Import bookmarks file", JOptionPane.OK_CANCEL_OPTION );
        if (r == JOptionPane.OK_OPTION) {
            InputStream in = null;
            try {
                ProgressMonitor mon = DasProgressPanel.createFramed("importing bookmarks");
                if ( gui.getRemote().isSelected() ) {
                    parent.getBookmarksManager().getModel().addRemoteBookmarks(bookmarksFile);
                    parent.getBookmarksManager().reload();
                } else {
                    in = DataSetURI.getInputStream(DataSetURI.getURIValid(bookmarksFile), mon);
                    ByteArrayOutputStream boas=new ByteArrayOutputStream();
                    WritableByteChannel dest = Channels.newChannel(boas);
                    ReadableByteChannel src = Channels.newChannel(in);
                    DataSourceUtil.transfer(src, dest);
                    String sin= new String( boas.toByteArray() );
                    List<Bookmark> books= Bookmark.parseBookmarks(sin);
                    parent.getBookmarksManager().getModel().importList( books );
                }
                parent.setMessage( "imported bookmarks file "+bookmarksFile );
            } catch (BookmarksException ex) {
                logger.log(Level.SEVERE, ex.getMessage(), ex);
                parent.applicationModel.showMessage( "Semantic Error parsing "+bookmarksFile+ "\n"+ex.getMessage(), "Error in import bookmarks", JOptionPane.WARNING_MESSAGE );
            } catch (SAXException ex) {
                logger.log(Level.SEVERE, ex.getMessage(), ex);
                parent.applicationModel.showMessage( "XML Error parsing "+bookmarksFile+ "\n"+ex.getMessage(), "Error in import bookmarks", JOptionPane.WARNING_MESSAGE );
            } catch (URISyntaxException ex) {
                logger.log(Level.SEVERE, ex.getMessage(), ex);
            } catch (FileNotFoundException ex ) {
                parent.applicationModel.showMessage( "File not found: "+bookmarksFile, "Error in import bookmarks", JOptionPane.WARNING_MESSAGE );
            } catch (IOException ex) {
                logger.log(Level.SEVERE, ex.getMessage(), ex);
                parent.applicationModel.showMessage( "I/O Error with "+bookmarksFile, "Error in import bookmarks", JOptionPane.WARNING_MESSAGE );
            } finally {
                try {
                    if ( in!=null ) in.close();
                } catch (IOException ex) {
                    logger.log(Level.SEVERE, ex.getMessage(), ex);
                }
            }
        }

    }


    private static void handleAddElementDialog( final AddPlotElementDialog dia, final Application dom, final ApplicationModel applicationModel) {
        Plot plot = null;
        PlotElement pelement = null;
        int modifiers = dia.getModifiers();
        if ( (modifiers & KeyEvent.CTRL_MASK) == KeyEvent.CTRL_MASK && (modifiers & KeyEvent.SHIFT_MASK) == KeyEvent.SHIFT_MASK ) {
            // reserve this for plot above, which we'll add soon.
            plot = dom.getController().addPlot(LayoutConstants.ABOVE);
            pelement = null;
        } else if ((modifiers & KeyEvent.CTRL_MASK) == KeyEvent.CTRL_MASK) {
            // new plot
            plot = null;
            pelement = null;
            //nothing
        } else if ((modifiers & KeyEvent.SHIFT_MASK) == KeyEvent.SHIFT_MASK) {
            // overplot
            plot = dom.getController().getPlot();
        } else {
            pelement = dom.getController().getPlotElement();
        }
        final Plot lplot= plot;
        final PlotElement lpelement= pelement;

        final String[] uris;
        final String[] filters;
        switch (dia.getDepCount()) {
            case 0:
                uris= new String[] {  dia.getPrimaryDataSetSelector().getValue() };
                filters= new String[] { dia.getPrimaryFilters() };
            break;  
            case 1:
                uris= new String[] {  dia.getSecondaryDataSetSelector().getValue(), dia.getPrimaryDataSetSelector().getValue() };
                filters= new String[] { dia.getSecondaryFilters(), dia.getPrimaryFilters() };
            break;  
            case 2:
                uris= new String[] {  dia.getSecondaryDataSetSelector().getValue(), dia.getTertiaryDataSetSelector().getValue(), dia.getPrimaryDataSetSelector().getValue() };
                filters= new String[] { dia.getSecondaryFilters(), dia.getTertiaryFilters(), dia.getPrimaryFilters() };
            break;  
            default:
                throw new IllegalArgumentException("this can't happen");
        }                        
        
        int depCount= dia.getDepCount();
        
        final String lock= "plotWithSlice";
        dom.getController().registerPendingChange( dom, lock );
        
        try {
            Runnable run;
            switch (depCount) {
                case 0:
                    String val= uris[0];
                    if ( val.endsWith(".vap") ) {
                        mergeVap(dom,lplot, lpelement, val);
                    } else {
                        final String lval= val;
                        run= new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    String uri= lval;
                                    DataSourceFactory factory = DataSetURI.getDataSourceFactory( DataSetURI.getURI(uri), new NullProgressMonitor() );
                                    if ( factory==null ) {
                                        if ( uri.startsWith("vap+internal:") ) { // allow testing.
                                            DataSourceFilter dsf= dom.getController().addDataSourceFilter();
                                            dsf.setUri( uri );
                                            dom.getController().addPlotElement( lplot, dsf );
                                            return;
                                        } else {
                                            throw new IllegalArgumentException("unable to resolve URI: "+uri);
                                        }
                                    }
                                    List<String> problems= new ArrayList<>();
                                    while ( factory.reject( uri, problems, new NullProgressMonitor() ) ) {
                                        dia.setTitle("Add Plot, URI was rejected...");
                                        
                                        dia.setMessagesLabelText("The URI was rejected.  Verify that it is correct using the inspect button (right).");
                                        
                                        WindowManager.getInstance().showModalDialog(dia);

                                        if ( dia.isCancelled() ) {
                                            return;
                                        }
                                        String val= dia.getPrimaryDataSetSelector().getValue();
                                        uri= val;
                                    }
                                    PlotElement pelement= dom.getController().doplot(lplot, lpelement, lval );
                                    DataSourceFilter dsf= (DataSourceFilter)DomUtil.getElementById( dom, pelement.getDataSourceFilterId() );
                                    if ( dia.getPrimaryFilters().length()>0 ) dsf.setFilters(dia.getPrimaryFilters());
   
                                } catch ( IOException | URISyntaxException ex ) {
                                    applicationModel.showMessage( ex.getMessage(), "Illegal Argument", JOptionPane.ERROR_MESSAGE );
                                } finally {
                                    dom.getController().changePerformed( dom, lock );
                                }
                            }
                        };
                        //new Thread(run).start(); //
                        run.run();
                    }
                    break;
                case 1:
                    applicationModel.addRecent(uris[0]);
                    applicationModel.addRecent(uris[1]);
                    run= new Runnable() {
                        @Override
                        public void run() {
                            try {
                                dom.getController().performingChange( dom, lock );
                                PlotElement pele= dom.getController().doplot(lplot, lpelement, uris[0], uris[1] );
                                DataSourceFilter dsf= (DataSourceFilter)DomUtil.getElementById( dom, pele.getDataSourceFilterId() );
                                List<DataSourceFilter> dsfs= DomUtil.getParentsFor( dom, dsf.getUri() );
                                if ( dsfs.size()==2 && dsfs.get(0)!=null && dsfs.get(1)!=null ) {
                                    if ( filters[0].length()>0 ) dsfs.get(0).setFilters( filters[0] );
                                    if ( filters[1].length()>0 ) dsfs.get(1).setFilters( filters[1] );
                                }                    
                                dom.getController().setFocusUri( dom.getController().getDataSourceFilterFor(pele).getUri());
                            } finally {
                                dom.getController().changePerformed( dom, lock );
                            }
                        }
                    };
                    new Thread(run).start();
                    break;
                case 2:
                    applicationModel.addRecent(uris[0]);
                    applicationModel.addRecent(uris[1]);
                    applicationModel.addRecent(uris[2]);
                    run= new Runnable() {
                        @Override
                        public void run() {            
                            try {
                                dom.getController().performingChange( dom, lock );
                                PlotElement pele= dom.getController().doplot(lplot, lpelement, uris[0], uris[1], uris[2] );
                                DataSourceFilter dsf= (DataSourceFilter)DomUtil.getElementById( dom, pele.getDataSourceFilterId() );
                                List<DataSourceFilter> dsfs= DomUtil.getParentsFor( dom, dsf.getUri() );
                                if ( dsfs.size()==3 && dsfs.get(0)!=null && dsfs.get(1)!=null && dsfs.get(2)!=null ) {
                                    if ( filters[0].length()>0 ) dsfs.get(0).setFilters( filters[0] );
                                    if ( filters[1].length()>0 ) dsfs.get(1).setFilters( filters[1] );
                                    if ( filters[2].length()>0 ) dsfs.get(2).setFilters( filters[2] );
                                } 
                                dom.getController().setFocusUri( dom.getController().getDataSourceFilterFor(pele).getUri());
                            } finally {
                                dom.getController().changePerformed( dom, lock );
                            }
                                
                        }
                    };
                    new Thread(run).start();
                    break;
            //if (pelement == null) {
            //    pelement = dom.getController().addPlotElement(plot, null);
            //}
                case -1:
                    break;
                default:
                    break;
            }
        } catch ( IllegalArgumentException ex ) { // TODO: the IllegalArgumentException is wrapped in a RuntimeException, I don't know why.  I should have MalformedURIException
            applicationModel.showMessage( ex.getMessage(), "Illegal Argument", JOptionPane.ERROR_MESSAGE );
        }
    }

    /**
     * support for binding two plot axes.
     * Set log first since we might tweak range accordingly.
     * @param dstPlot
     * @param plot
     * @param axis
     * @param props null is old range and log.  list of properties to bind
     * @throws java.lang.IllegalArgumentException
     */
    private static void bindToPlotPeer( final ApplicationController controller, Plot dstPlot, Plot plot, Axis axis, String[] props) throws IllegalArgumentException {
        Axis targetAxis;
        if (plot.getXaxis() == axis) {
            targetAxis = dstPlot.getXaxis();
        } else if (plot.getYaxis() == axis) {
            targetAxis = dstPlot.getYaxis();
        } else if (plot.getZaxis() == axis) {
            targetAxis = dstPlot.getZaxis();
        } else {
            throw new IllegalArgumentException("this axis and plot don't go together");
        }
        if ( props==null ) {
            axis.setLog( targetAxis.isLog() );
            axis.setRange( targetAxis.getRange() );
            props= new String[] { Axis.PROP_LOG, Axis.PROP_RANGE };
        }
        for ( String p : props ) {
            controller.bind(targetAxis, p, axis, p ); 
        }
    }



    protected static void addAxisContextMenuItems( final ApplicationController controller, final DasPlot dasPlot, final PlotController plotController, final Plot plot, final Axis axis) {

        final DasAxis dasAxis = axis.getController().getDasAxis();
        final DasMouseInputAdapter mouseAdapter = dasAxis.getDasMouseInputAdapter();

        List<JMenuItem> expertMenuItems= new ArrayList();

        mouseAdapter.removeMenuItem("Properties");

        JMenuItem item;

        item= new JMenuItem(new AbstractAction("Axis Properties") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);                
                PropertyEditor pp = new PropertyEditor(axis);
                pp.showDialog(dasAxis.getCanvas());
            }
        });
        mouseAdapter.addMenuItem(item);
        expertMenuItems.add(item);
        
        mouseAdapter.addMenuItem(new JSeparator());

        item= new JMenuItem( new AbstractAction("Reset Zoom" ) {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);                
                if ( plot.getZaxis()==axis ) {
                    AutoplotUtil.resetZoomZ( controller.getApplication(), plot );
                } else {
                    if ( axis==plot.getXaxis() ) {
                        AutoplotUtil.resetZoomX( controller.getApplication(), plot );
                    } else {
                        AutoplotUtil.resetZoomY( controller.getApplication(), plot );
                    }
                }
            }            
        } );
        mouseAdapter.addMenuItem(item);
                
        if (axis == plot.getXaxis()) {
            JMenu addPlotMenu = new JMenu("Add Plot");
            mouseAdapter.addMenuItem(addPlotMenu);

            item = new JMenuItem(new AbstractAction("Bound Plot Below") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    org.das2.util.LoggerManager.logGuiEvent(e);                    
                    controller.copyPlot(plot, true, false, true);
                }
            });
            item.setToolTipText("add a new plot below.  The plot's x axis will be bound to this plot's x axis");
            addPlotMenu.add(item);
            expertMenuItems.add( addPlotMenu );
        }

        JMenu bindingMenu = new JMenu("Binding");

        mouseAdapter.addMenuItem(bindingMenu);

        if (axis == plot.getXaxis()) {
            item = new JMenuItem(new AbstractAction("Add Binding to Application Time Range") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    org.das2.util.LoggerManager.logGuiEvent(e);                
                    DatumRange dr= controller.getApplication().getTimeRange();
                    if ( dr==Application.DEFAULT_TIME_RANGE ) {
                        controller.getApplication().setTimeRange( dr.next() );
                        controller.getApplication().setTimeRange( dr.next().previous() ); // so it accepts the value and fires event
                    }
                    controller.bind(controller.getApplication(), Application.PROP_TIMERANGE, axis, Axis.PROP_RANGE);
                }
            });
            bindingMenu.add(item);
        }

        item = new JMenuItem(new AbstractAction("Bind Range to Plot Above") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);                
                Plot dstPlot = controller.getPlotAbove(plot);
                if (dstPlot == null) {
                    controller.setStatus("warning: no plot above");
                } else {
                    bindToPlotPeer(controller,dstPlot, plot, axis, null );
                }
            }
        });
        bindingMenu.add(item);
        item = new JMenuItem(new AbstractAction("Bind Range to Plot Below") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);                
                Plot dstPlot = controller.getPlotBelow(plot);
                if (dstPlot == null) {
                    controller.setStatus("warning: no plot below");
                } else {
                    bindToPlotPeer(controller,dstPlot, plot, axis, null );
                }
            }
        });
        bindingMenu.add(item);
        item = new JMenuItem(new AbstractAction("Bind Range to Plot to the Right") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);                
                Plot dstPlot = controller.getNextPlotHoriz(plot,LayoutConstants.RIGHT);
                if (dstPlot == null) {
                    controller.setStatus("warning: no plot to the right");
                } else {
                    bindToPlotPeer(controller,dstPlot, plot, axis, null );
                }
            }
        });
        bindingMenu.add(item);
        item = new JMenuItem(new AbstractAction("Bind Range to Plot to the Left") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);                
                Plot dstPlot = controller.getNextPlotHoriz(plot,LayoutConstants.LEFT);
                if (dstPlot == null) {
                    controller.setStatus("warning: no plot to the left");
                } else {
                    bindToPlotPeer(controller,dstPlot, plot, axis, null );
                }
            }
        });
        bindingMenu.add(item);

        item = new JMenuItem(new AbstractAction("Bind Scale to Plot Above") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);                
                Plot dstPlot = controller.getPlotAbove(plot);
                if (dstPlot == null) {
                    controller.setStatus("warning: no plot above");
                } else {
                    bindToPlotPeer(controller,dstPlot, plot, axis, new String[] { Axis.PROP_LOG, Axis.PROP_SCALE });
                }
            }
        });
        bindingMenu.add(item);
        
        item = new JMenuItem(new AbstractAction("Bind Scale to Plot Below") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);                
                Plot dstPlot = controller.getPlotBelow(plot);
                if (dstPlot == null) {
                    controller.setStatus("warning: no plot below");
                } else {
                    bindToPlotPeer(controller,dstPlot, plot, axis, new String[] { Axis.PROP_LOG, Axis.PROP_SCALE });
                }
            }
        });
        bindingMenu.add(item);

        item = new JMenuItem(new AbstractAction("Bind Scale to Opposite Axis") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);                
                controller.bind( plot.getXaxis(), Axis.PROP_LOG, plot.getYaxis(), Axis.PROP_LOG );
                controller.bind( plot.getXaxis(), Axis.PROP_SCALE, plot.getYaxis(), Axis.PROP_SCALE );
            }
        });
        bindingMenu.add(item);
        
        item = new JMenuItem(new AbstractAction("Remove Bindings") {
            @Override            
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);                
                BindingModel[] bms= controller.getBindingsFor(axis);
                controller.unbind(axis);  // TODO: check for application timerange
                controller.setStatus("removed "+bms.length+" bindings");
            }
        });
        item.setToolTipText("remove any plot and plot element property bindings");
        bindingMenu.add(item);

        expertMenuItems.add(bindingMenu);

        JMenu connectorMenu = new JMenu("Connector");

        mouseAdapter.addMenuItem(connectorMenu);

        item = new JMenuItem(new AbstractAction("Add Connector to Plot Above") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);                
                Plot dstPlot = controller.getPlotAbove(plot);
                if (dstPlot == null) {
                    controller.setStatus("warning: no plot above");
                } else {
                    controller.addConnector(dstPlot, plot);
                }
            }
        });
        connectorMenu.add(item);

        item = new JMenuItem(new AbstractAction("Add Connector to Plot Below") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);                
                Plot dstPlot = controller.getPlotBelow(plot);
                if (dstPlot == null) {
                    controller.setStatus("warning: no plot below");
                } else {
                    controller.addConnector(plot,dstPlot);
                }
            }
        });
        connectorMenu.add(item);

        item = new JMenuItem(new AbstractAction("Delete Connectors") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);                
                Application dom= plot.getController().getApplication();
                for (Connector c : DomUtil.asArrayList(dom.getConnectors())) {
                    if (c.getPlotA().equals(plot.getId()) || c.getPlotB().equals(plot.getId())) {
                        dom.getController().deleteConnector(c);
                    }
                }
                dom.getController().getCanvas().getController().getDasCanvas().repaint();
            }
        });
        
        connectorMenu.add(item);

        expertMenuItems.add(connectorMenu);

        if ( axis.getController().getDasAxis().isHorizontal() ) {
            item= new JMenuItem( new AbstractAction("Add Additional Ticks from...") {
                @Override
                public void actionPerformed(ActionEvent e) {
                    org.das2.util.LoggerManager.logGuiEvent(e);                
                    String uri= plot.getTicksURI();
                    if ( uri.startsWith("class:org.autoplot.tca.UriTcaSource:") ) {
                        uri= uri.substring("class:org.autoplot.tca.UriTcaSource:".length());
                    }
                    if ( UnitsUtil.isTimeLocation( axis.getController().getDasAxis().getDatumRange().getUnits() ) ) {
                        String nuri= DataSetURI.resetUriTsbTime(uri,axis.getController().getDasAxis().getDatumRange());
                        if ( nuri!=null && !nuri.equals(uri) ) {
                            uri= nuri;
                        }
                    }
                    TcaElementDialog dia= new TcaElementDialog( (JFrame)SwingUtilities.getWindowAncestor( controller.getDasCanvas().getParent()), true );
                    dia.getPrimaryDataSetSelector().setValue(uri);
                    dia.getPrimaryDataSetSelector().setTimeRange( axis.getController().getDasAxis().getDatumRange() );
                    dia.setTitle( "Add additional ticks" );
                    dia.setVisible(true);
                    if (dia.isCancelled()) {
                        return;
                    }
                    uri= dia.getPrimaryDataSetSelector().getValue();
                    if ( uri.length()==0 ) {
                        plot.setTicksURI("");
                    } else {
                        plot.setTicksURI(uri);
                    }
                }
            });
            mouseAdapter.addMenuItem(item);
            expertMenuItems.add(item);

        }
        
        item= new JMenuItem( new AbstractAction("Reset axis units to..." ) {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);                
                Units u= dasAxis.getUnits();
                Units[] uu= u.getConvertibleUnits();

                Component p= (JFrame)SwingUtilities.getWindowAncestor( controller.getDasCanvas().getParent());
                
                if ( uu.length<1 ) {
                    JOptionPane.showMessageDialog( p, "No conversions found from \""+u+"\"");
                } else {
                    JPanel p1= new JPanel();
                    p1.setLayout( new BoxLayout(p1,BoxLayout.Y_AXIS) );
                    p1.setAlignmentY(0.f);
                    p1.setAlignmentX(0.f);
                    JLabel l= new JLabel("Axis units are \""+u+"\"");
                    l.setAlignmentX(0.f);
                    p1.add(l);
                    l= new JLabel("Reset axis units to:");
                    l.setAlignmentX(0.f);
                    p1.add(l);
                    JComboBox cb= new JComboBox(uu);
                    cb.setAlignmentX(0.f);
                    p1.add(cb);
                    if ( JOptionPane.OK_OPTION==JOptionPane.showConfirmDialog( p,
                                p1,
                                "Reset axis units", JOptionPane.OK_CANCEL_OPTION ) ) {
                        Units nu= (Units)cb.getSelectedItem();
                        axis.getController().resetAxisUnits(nu);
                    }
                }
            }            
        } );
        mouseAdapter.addMenuItem(item);
        expertMenuItems.add(item);
            
        List<JMenuItem> expertMenuItemsList= new ArrayList( Arrays.asList( plotController.getExpertMenuItems() ) );
        expertMenuItemsList.addAll(expertMenuItems);

        plotController.setExpertMenuItems( expertMenuItemsList.toArray(new JMenuItem[expertMenuItemsList.size()] )  );

    }

    public static void pasteClipboardIntoPlot( Component app, ApplicationController controller, Plot newP ) throws HeadlessException {
        try {
            Clipboard clpbrd= Toolkit.getDefaultToolkit().getSystemClipboard();
            String s;
            if ( clpbrd.isDataFlavorAvailable(DataFlavor.stringFlavor) ) {
                s= (String) clpbrd.getData(DataFlavor.stringFlavor);
                if ( !s.startsWith("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<vap") ) {
                    JOptionPane.showMessageDialog(app,"Use \"Edit Plot\"->\"Copy Plot to Clipboard\"");
                    return;
                }
            } else {
                JOptionPane.showMessageDialog(app,"Use \"Edit Plot\"->\"Copy Plot to Clipboard\"");
                return;
            }
            
            List<PlotElement> pes= controller.getPlotElementsFor(newP);
            for ( PlotElement pe: pes ) {
                controller.deletePlotElement(pe);
            }
            
            Application state= (Application)StatePersistence.restoreState(new ByteArrayInputStream(s.getBytes()));
            Plot p= state.getPlots(0);
            newP.syncTo(p,Arrays.asList("id","rowId","columnId") );
            Map<String,String> nameMap= new HashMap<>();
            nameMap.put( p.getId(), newP.getId() );
            //List<DataSourceFilter> unresolved= new ArrayList<>();
            for ( int i=0; i<state.getDataSourceFilters().length; i++ ) {
                DataSourceFilter newDsf= controller.addDataSourceFilter();
                DataSourceFilter stateDsf= state.getDataSourceFilters(i);
                if ( stateDsf.getUri().startsWith("vap+internal:") ) {
                    //unresolved.add(stateDsf);
                } else {
                    newDsf.syncTo(state.getDataSourceFilters(i),Collections.singletonList("id"));   
                    state.setDataSourceFilters(i,null); // mark as done
                }
                nameMap.put( stateDsf.getId(), newDsf.getId() );
            }
            for ( int i=0; i<state.getDataSourceFilters().length; i++ ) {
                DataSourceFilter stateDsf= state.getDataSourceFilters(i);
                if ( stateDsf!=null ) {
                    String uri= stateDsf.getUri();
                    String[] children= uri.substring(13).split(",");
                    StringBuilder sb= new StringBuilder( "vap+internal:" );
                    for ( int j=0; j<children.length; j++ ) {
                        if (j>0) sb.append(",");
                        sb.append( nameMap.get(children[j]) );
                    }
                    stateDsf.setUri(sb.toString());
                    DataSourceFilter newDsf= (DataSourceFilter)DomUtil.getElementById( controller.getApplication(), nameMap.get(stateDsf.getId()) );
                    newDsf.syncTo( stateDsf,Collections.singletonList("id"));   
                }
            }
            Application theApp= controller.getApplication();
            for ( int i=0; i<state.getPlotElements().length; i++ ) {
                PlotElement pe1= state.getPlotElements(i);
                DataSourceFilter dsf1= 
                        (DataSourceFilter) DomUtil.getElementById( theApp,nameMap.get(pe1.getDataSourceFilterId()) );
                Plot plot1= (Plot) DomUtil.getElementById( theApp, nameMap.get(pe1.getPlotId()) );
                PlotElement pe= controller.addPlotElement( plot1, dsf1 );
                pe.syncTo( pe1, Arrays.asList( "id", "plotId", "dataSourceFilterId") );
                if ( i==0 ) {
                    plot1.setAutoBinding(true); // kludge
                    plot1.getController().setAutoBinding(true); // TODO: check on why there are two autoBinding properties
                }
                pe.setPlotDefaults( pe1.getPlotDefaults() );

            }


        } catch (UnsupportedFlavorException | IOException ex) {
            Logger.getLogger(GuiSupport.class.getName()).log(Level.SEVERE, null, ex);
        }
    }    
    /**
     * Add items to the plot context menu, such as properties and add plot.
     * @param controller
     * @param plot
     * @param plotController
     * @param domPlot
     */
    static void addPlotContextMenuItems( final AutoplotUI app, final ApplicationController controller, final DasPlot plot, final PlotController plotController, final Plot domPlot) {

        plot.getDasMouseInputAdapter().addMouseModule(new MouseModule(plot, new PointSlopeDragRenderer(plot, plot.getXAxis(), plot.getYAxis()), "Slope"));

        plot.getDasMouseInputAdapter().removeMenuItem("Dump Data");
        plot.getDasMouseInputAdapter().removeMenuItem("Properties");

        JMenuItem item;

        List<JMenuItem> expertMenuItems= new ArrayList();

        JMenuItem mi;
        
        mi= new JMenuItem(new AbstractAction("Plot Properties") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);                
                PropertyEditor pp = new PropertyEditor(domPlot);
                JDialog d= pp.getDialog(plot.getCanvas());
                WindowManager.getInstance().recallWindowSizePosition(d);
                d.setVisible(true);
                WindowManager.getInstance().recordWindowSizePosition(d);
            }
        });
        plot.getDasMouseInputAdapter().addMenuItem(mi);
        expertMenuItems.add( mi );

        mi= new JMenuItem(new AbstractAction("Plot Element Properties") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                PlotElement p = controller.getPlotElement();
                PropertyEditor pp = new PropertyEditor(p);
                JDialog d= pp.getDialog(plot.getCanvas());
                WindowManager.getInstance().recallWindowSizePosition(d);
                d.setVisible(true);
                WindowManager.getInstance().recordWindowSizePosition(d);
            }
        } );
        plot.getDasMouseInputAdapter().addMenuItem( mi );
        expertMenuItems.add( mi );


        JMenuItem panelPropsMenuItem= new JMenuItem(new AbstractAction("Plot Element Style Properties") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                PlotElement p = controller.getPlotElement();
                PlotStylePanel.StylePanel editorPanel= getStylePanel( p.getRenderType() );
                editorPanel.doElementBindings(p);
                AutoplotUtil.showMessageDialog( app, editorPanel, p.getRenderType() + " Style", JOptionPane.OK_OPTION );
            }
        });
        plotController.setPlotElementPropsMenuItem(panelPropsMenuItem);        
        plot.getDasMouseInputAdapter().addMenuItem(panelPropsMenuItem);
        expertMenuItems.add(panelPropsMenuItem);

        plot.getDasMouseInputAdapter().addMenuItem(new JSeparator());

        final JMenu ezMenu= GuiSupport.createEZAccessMenu(domPlot);
        ezMenu.addMenuListener( new MenuListener() {
            @Override
            public void menuSelected(MenuEvent e) {
                PlotElement pe= app.dom.getController().getPlotElement();
                QDataSet ds;
                if ( pe!=null ) {
                    ds= pe.getController().getDataSet();
                } else {
                    return;
                }
                Map<String,RenderType> tt= getRenderTypeForString();
                for ( int i=0; i<ezMenu.getItemCount(); i++ ) {
                    if ( ezMenu.getItem(i) instanceof JCheckBoxMenuItem ) {
                        JCheckBoxMenuItem mi= ((JCheckBoxMenuItem)ezMenu.getItem(i));
                        RenderType rt= tt.get( mi.getText() );
                        if ( rt.equals(pe.getRenderType()) ) {
                            mi.setSelected(true);
                        } else {
                            mi.setSelected(false);
                        }
                        if ( pe.getController().getParentPlotElement()!=null ) {
                            ds= pe.getController().getParentPlotElement().getController().getDataSet();
                            if ( ds==null || RenderType.acceptsData( rt, ds ) ) {
                                mi.setEnabled(true);
                            } else {
                                mi.setEnabled(false);
                            }
                        } else {
                            if ( ds==null || RenderType.acceptsData( rt, ds ) ) {
                                mi.setEnabled(true);
                            } else {
                                mi.setEnabled(false);
                            }
                        }
                    }
                }
            }

            @Override
            public void menuDeselected(MenuEvent e) {
            }

            @Override
            public void menuCanceled(MenuEvent e) {                
            }

        });
        plot.getDasMouseInputAdapter().addMenuItem(ezMenu);
        expertMenuItems.add(ezMenu);

        JMenu addPlotMenu = new JMenu("Add Plot");
        plot.getDasMouseInputAdapter().addMenuItem(addPlotMenu);

        item = new JMenuItem(new AbstractAction("Copy Plot Elements Down") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                Runnable run= new Runnable() {
                    @Override
                    public void run() {
                        Plot newPlot= controller.copyPlotAndPlotElements(domPlot, null, false, false);
                        Application dom= domPlot.getController().getApplication();
                        List<BindingModel> bms= dom.getController().findBindings( dom, Application.PROP_TIMERANGE, domPlot.getXaxis(), Axis.PROP_RANGE );
                        if ( bms.size()>0 && UnitsUtil.isTimeLocation( newPlot.getXaxis().getRange().getUnits() ) ) {
                            controller.bind( controller.getApplication(), Application.PROP_TIMERANGE, newPlot.getXaxis(), Axis.PROP_RANGE );
                        }
                    }
                };
                new Thread(run,"copyPlotElementsDown").start();
            }
        });
        item.setToolTipText("make a new plot below, and copy the plot elements into it.  New plot is bound by the x axis.");
        addPlotMenu.add(item);

        item = new JMenuItem( new  AbstractAction("Paste Plot From Clipboard") {
            @Override
            public void actionPerformed(ActionEvent e) {
                Plot newP= controller.addPlot( domPlot, LayoutConstants.BELOW );
                pasteClipboardIntoPlot(app,controller,newP);
            }
        });
        item.setToolTipText("Paste the plot in the system clipboard.");
        addPlotMenu.add(item);
            
        item = new JMenuItem( new AbstractAction("Add Inset Plot") {
            @Override
            public void actionPerformed(ActionEvent e) {
                controller.addPlot( "50%,100%-2em", "2em,50%" );
            }
        } );
        item.setToolTipText("Add a plot at an arbitrary position.");
        addPlotMenu.add(item);
        
//        item = new JMenuItem(new AbstractAction("Copy Plot Elements Right") {
//
//            public void actionPerformed(ActionEvent e) {
//                DomOps.copyPlotAndPlotElements(domPlot,true,false,false,LayoutConstants.RIGHT);
//            }
//        });
//        item.setToolTipText("make a new plot to the right, and copy the plot elements into it.");
//        addPlotMenu.add(item);

        item = new JMenuItem(new AbstractAction("Context Overview") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                Runnable run= new Runnable() {
                    @Override
                    public void run() {
                        domPlot.getController().contextOverview();
                    }
                };
                new Thread(run,"contextOverview").start();
            }
        });
        item.setToolTipText("make a new plot, and copy the plot elements into it.  The plot is not bound,\n" +
                "and a connector is drawn between the two.  The panel uris are bound as well.");
        
        addPlotMenu.add(item);
        item = new JMenuItem(new AbstractAction("New Location (URI)...") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                Runnable run= new Runnable() {
                    @Override
                    public void run() {
                        app.dom.getController().setPlot(domPlot);
                        app.support.addPlotElement("New Location (URI)");
                    }
                };
                SwingUtilities.invokeLater(run);
                //run.run();
            }
        });
        item.setToolTipText("change URI or add plot");
        addPlotMenu.add(item);

        expertMenuItems.add(addPlotMenu);

        JMenu editPlotMenu = new JMenu("Edit Plot");
        plot.getDasMouseInputAdapter().addMenuItem(editPlotMenu);
        controller.fillEditPlotMenu(editPlotMenu, domPlot);
        expertMenuItems.add(editPlotMenu);

        JMenu panelMenu = new JMenu("Edit Plot Element");
        plot.getDasMouseInputAdapter().addMenuItem(panelMenu);
        expertMenuItems.add(panelMenu);

        item = new JMenuItem(new AbstractAction("Move to Plot Above") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                PlotElement pelement = controller.getPlotElement();
                Plot plot = controller.getPlotFor(pelement);
                Plot dstPlot = controller.getPlotAbove(plot);
                if (dstPlot == null) {
                    dstPlot = controller.addPlot(LayoutConstants.ABOVE);
                    pelement.setPlotId(dstPlot.getId());
                } else {
                    pelement.setPlotId(dstPlot.getId());
                }
            }
        });
        panelMenu.add(item);
        expertMenuItems.add(panelMenu);

        item = new JMenuItem(new AbstractAction("Insert New Plot Above") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                PlotElement pelement = controller.getPlotElement();
                Plot dstPlot = controller.addPlot(LayoutConstants.ABOVE);
                pelement.setPlotId(dstPlot.getId());
            }
        });
        panelMenu.add(item);
        expertMenuItems.add(panelMenu);
        
        item = new JMenuItem(new AbstractAction("Insert New Plot Below") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                PlotElement pelement = controller.getPlotElement();
                Plot dstPlot = controller.addPlot(LayoutConstants.BELOW);
                pelement.setPlotId(dstPlot.getId());
            }
        });
        panelMenu.add(item);
        expertMenuItems.add(panelMenu);
        

        item = new JMenuItem(new AbstractAction("Move to Plot Below") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                PlotElement pelement = controller.getPlotElement();
                Plot plot = controller.getPlotFor(pelement);
                Plot dstPlot = controller.getPlotBelow(plot);
                if (dstPlot == null) {
                    dstPlot = controller.addPlot(LayoutConstants.BELOW);
                    pelement.setPlotId(dstPlot.getId());
                    controller.bind(plot.getXaxis(), Axis.PROP_RANGE, dstPlot.getXaxis(), Axis.PROP_RANGE);
                } else {
                    pelement.setPlotId(dstPlot.getId());
                }
            }
        });
        panelMenu.add(item);
        expertMenuItems.add(item);

        item = new JMenuItem(new AbstractAction("Delete Plot Element") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                PlotElement pelement = controller.getPlotElement();
                if (controller.getApplication().getPlotElements().length < 2) {
                    DataSourceFilter dsf= controller.getDataSourceFilterFor(controller.getApplication().getPlotElements(0));
                    dsf.setUri("");
                    pelement.setLegendLabelAutomatically(""); //TODO: null should reset everything.
                    pelement.setActive(true);
                    return;
                }
                controller.deletePlotElement(pelement);
            }
        });
        panelMenu.add(item);
        expertMenuItems.add(item);

        item=  new JMenuItem(new AbstractAction("Move Plot Element Below Others") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                PlotElement pelement = controller.getPlotElement();
                Plot p= pelement.getController().getApplication().getController().getPlotFor(pelement);
                p.getController().toBottom(pelement);
            }
        });
        panelMenu.add(item);
        expertMenuItems.add(item);

        JMenuItem editDataMenu = new JMenuItem(new AbstractAction("Edit Data Source") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                GuiSupport.editPlotElement( controller.getApplicationModel(), plot );
            }
        });
        expertMenuItems.add(editDataMenu);

        plot.getDasMouseInputAdapter().addMenuItem(editDataMenu);

        plot.getDasMouseInputAdapter().addMenuItem(new JSeparator());

        plot.getDasMouseInputAdapter().addMenuItem(new JMenuItem(new AbstractAction("Reset Zoom") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                plotController.resetZoom(true, true, true);
            }
        }));

        plot.getDasMouseInputAdapter().addMenuItem(new JMenuItem(new AbstractAction("Add Annotation...") {
            @Override
            public void actionPerformed(ActionEvent e) {
                org.das2.util.LoggerManager.logGuiEvent(e);
                AddAnnotationDialog dia= new AddAnnotationDialog();
                dia.setPointAtX( plot.getXAxis().invTransform( plot.getDasMouseInputAdapter().getMousePressPositionOnCanvas().x ) );
                dia.setPointAtY( plot.getYAxis().invTransform( plot.getDasMouseInputAdapter().getMousePressPositionOnCanvas().y ) );
                if ( JOptionPane.OK_OPTION==AutoplotUtil.showConfirmDialog( app, dia, "Add Annotation", JOptionPane.OK_CANCEL_OPTION ) ) {
                    Annotation ann= controller.addAnnotation( domPlot, dia.getText() );
                    dia.configure(ann);
                    ann.setAnchorOffset("1em,1em");
                    ann.setBorderType(BorderType.ROUNDED_RECTANGLE);
                    ann.setFontSize("1.4em");
                }
            }
        }));
        
        plot.getDasMouseInputAdapter().addMenuItem(new JSeparator());

        plotController.setExpertMenuItems( expertMenuItems.toArray(new JMenuItem[expertMenuItems.size()] ) );

        plot.getDasMouseInputAdapter().setMenuLabel(domPlot.getId());        

    }

    protected void doInspectVap() {
        Preferences prefs = AutoplotSettings.settings().getPreferences( AutoplotSettings.class);

        String currentDirectory = prefs.get( AutoplotSettings.PREF_LAST_OPEN_VAP_FOLDER, prefs.get(AutoplotSettings.PREF_LAST_OPEN_FOLDER, new File(System.getProperty("user.home")).toString() ) );
        String lcurrentFile=  prefs.get( AutoplotSettings.PREF_LAST_OPEN_VAP_FILE, "" );
        
        JFileChooser chooser= new JFileChooser(currentDirectory);
        if ( lcurrentFile.length()>0 ) {
            chooser.setSelectedFile( new File( lcurrentFile ) );
        }
        
        FileFilter ff = new FileFilter() {

            @Override
            public boolean accept(File f) {
                String s= f.toString();
                if ( s==null ) return false; // Old Windows bug.  FINDBUGS OKAY
                return s.endsWith(".vap") || f.isDirectory();
            }

            @Override
            public String getDescription() {
                return "*.vap";
            }
        };
        chooser.addChoosableFileFilter(ff);

        chooser.setFileFilter(ff);
        
        if ( JFileChooser.APPROVE_OPTION==chooser.showOpenDialog(parent) ) {
            try {
                final File f= chooser.getSelectedFile();
                prefs.put(AutoplotSettings.PREF_LAST_OPEN_VAP_FOLDER, f.getParent() );
                prefs.put( AutoplotSettings.PREF_LAST_OPEN_VAP_FILE, f.toString() );
                final Application vap = (Application) StatePersistence.restoreState( f );
                PropertyEditor edit = new PropertyEditor(vap);
                edit.addSaveAction( new AbstractAction("Save") {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        org.das2.util.LoggerManager.logGuiEvent(e);
                        try {
                            StatePersistence.saveState(f, vap);
                        } catch (IOException ex) {
                            JOptionPane.showConfirmDialog( parent, "Unable to save to file: "+ f );
                        }
                    }
                });
                edit.showDialog(this.parent);
            } catch ( Exception ex ) {
                logger.log(Level.WARNING,ex.getMessage(),ex);
                JOptionPane.showMessageDialog( parent, "File does not appear to well-formatted .vap file" );
            }

        }
    }

    /**
     * show the pick font dialog.  The font chosen, is applied and returned, or null if cancel was pressed. 
     * 
     * @param parent dialog parent.
     * @param app the applicationModel with canvas.
     * @return
     */
    public static Font pickFont( Frame parent, ApplicationModel app ) {
        JFontChooser chooser = new JFontChooser( parent );
        String sci= Entities.decodeEntities("2 &times; 10E7  &aacute;");
        String greek= Entities.decodeEntities("Greek Symbols: &Alpha; &Beta; &Delta; &alpha; &beta; &delta; &pi; &rho; &omega;");
        String math= Entities.decodeEntities("Math Symbols: &sum; &plusmn;");

        chooser.setExampleText("Electron Differential Energy Flux\n2001-01-10 12:00\nExtended ASCII: "+sci+"\n"+greek+"\n"+math);
        chooser.setFontCheck( new JFontChooser.FontCheck() {
            @Override
            public String checkFont(Font c) {
                Object font= PdfGraphicsOutput.ttfFromNameInteractive(c);
                if ( font==PdfGraphicsOutput.READING_FONTS ) {
                    return "Checking which fonts are embeddable...";
                } else if ( font!=null ) {
                    return "PDF okay";
                } else {                    
                    return "Can not be embedded in PDF";
                }
            }
        });
        chooser.setFont(app.getCanvas().getBaseFont());
        chooser.setLocationRelativeTo(parent);
        if (chooser.showDialog() == JFontChooser.OK_OPTION) {
            return setFont( app, chooser.getFont() );
        } else {
            return null;
        }
    }

    /**
     * encapsulates the goofy logic about setting the font.
     * @param app
     * @param nf
     * @return the Font actually used.
     */
    public static Font setFont( ApplicationModel app, Font nf ) {
        app.getCanvas().setBaseFont(nf);
        Font f = app.getCanvas().getFont();
        app.getDocumentModel().getOptions().setCanvasFont( DomUtil.encodeFont(f) );
        return f;
    }
    /**
     * raise the application window
     * http://stackoverflow.com/questions/309023/howto-bring-a-java-window-to-the-front
     * This is not working for me on Ubuntu 10.04.
     * @param frame
     */
    public static void raiseApplicationWindow( java.awt.Frame frame ) {
        // http://stackoverflow.com/questions/309023/howto-bring-a-java-window-to-the-front
        frame.setVisible(true);
        int state = frame.getExtendedState();
        state &= ~JFrame.ICONIFIED;
        frame.setExtendedState(state);
        frame.setAlwaysOnTop(true); // security exception
        frame.toFront();
        frame.requestFocus();
        frame.setAlwaysOnTop(false); // security exception
    }

    /**
     * legacy code for adding examples to a text field.
     * @param tf
     * @param labels
     * @param tooltips
     * @return 
     */
    public static MouseAdapter createExamplesPopup( final JTextField tf, final String [] labels, final String[] tooltips ) {
        return new MouseAdapter() {
            private JMenuItem createMenuItem( final JTextField componentTextField, final String insert, String doc ) {
                JMenuItem result= new JMenuItem( new AbstractAction( insert ) {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        org.das2.util.LoggerManager.logGuiEvent(e);
                        String v= componentTextField.getText();
                        int i= componentTextField.getCaretPosition();
                        componentTextField.setText( v.substring(0,i) + insert + v.substring(i) );
                    }
                });
                if ( doc!=null ) result.setToolTipText(doc);
                return result;
            }
            void showPopup( MouseEvent ev ) {
                JPopupMenu processMenu;
                processMenu= new JPopupMenu();
                for ( int i=0; i<labels.length; i++ ) {
                    processMenu.add( createMenuItem( tf, labels[i], tooltips==null ? null : tooltips[i] ) );
                }
                processMenu.show(ev.getComponent(), ev.getX(), ev.getY());
            }

            @Override
            public void mousePressed(MouseEvent evt) {
                if ( evt.isPopupTrigger() ) {
                    showPopup(evt);
                }
            }

            @Override
            public void mouseReleased(MouseEvent evt) {
                if ( evt.isPopupTrigger() ) {
                    showPopup(evt);
                }
            }

        };
    }
}