package org.autoplot.jythonsupport.ui; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; import org.das2.datum.Datum; import org.das2.datum.DatumRange; import org.das2.datum.DatumRangeUtil; import org.das2.util.LoggerManager; import org.python.core.Py; import org.python.core.PyDictionary; import org.python.util.PythonInterpreter; import org.autoplot.datasource.DataSetSelector; import org.autoplot.datasource.TimeRangeTool; import org.autoplot.datasource.WindowManager; import org.autoplot.jythonsupport.JythonUtil; import org.das2.util.ColorUtil; import java.awt.Color; import java.awt.event.ActionListener; import java.net.URL; import java.util.regex.Pattern; import javax.swing.ImageIcon; import javax.swing.JColorChooser; import javax.swing.JFileChooser; import javax.swing.filechooser.FileFilter; import org.autoplot.datasource.RecentComboBox; import org.autoplot.jythonsupport.JythonUtil.ScriptDescriptor; import org.autoplot.jythonsupport.Param; import org.das2.datum.UnitsUtil; import org.das2.util.FileUtil; import org.python.core.PyString; /** * GUI component for controlling script parameters. * @author jbf */ public class ParametersFormPanel { FormData fd; Map params; public ParametersFormPanel() { fd= new FormData(); } private static final Logger logger= LoggerManager.getLogger("jython.form"); private static boolean isBoolean( List parms ) { return parms.size()==2 && ( parms.contains("T") && parms.contains("F") || parms.contains(0) && parms.contains(1) ); } private static JComponent getSpacer() { JComponent spacer= new JLabel(" "); spacer.setSize( new Dimension(20,16) ); spacer.setMinimumSize( new Dimension(20,16) ); spacer.setPreferredSize( new Dimension(20,16) ); return spacer; } /** * represent the data on the form. */ public static class FormData { ArrayList tflist; ArrayList paramsList; ArrayList deftsList; ArrayList deftObjectList; ArrayList typesList; public int count; /** * Convert the parameter to Jython types. *
         * T (TimeRange)
         * A (String)
         * F (Double or Integer)
         * R (URI)
         * L (URL) a resource URL (local file or web file).
         * D Datum
         * S DatumRange
         * 
* * @param interp the interpreter * @param param the param name * @param value the param value * @throws java.text.ParseException */ public void implement( PythonInterpreter interp, String param, String value ) throws ParseException { PyDictionary paramsDictionary= ((PyDictionary)interp.get( "params" )); for ( int i=0; i1 && value.charAt(0)!='\'' && value.charAt(value.length()-1)!='\'' ) { paramsDictionary.__setitem__( param, Py.java2py( value ) ); } else if (type.equals('D') ) { paramsDictionary.__setitem__( param, Py.java2py( ((Datum)deft).getUnits().parse(value) ) ); } else if ( type.equals('S') ) { paramsDictionary.__setitem__( param, Py.java2py( DatumRangeUtil.parseDatumRange(value,(DatumRange)deft ) ) ); } else if ( type.equals('U') ) { try { paramsDictionary.__setitem__( param, Py.java2py( new java.net.URI( value ) ) ); } catch (URISyntaxException ex) { throw new ParseException( "URI is not formed properly",0); } } else if ( type.equals('L') ) { try { paramsDictionary.__setitem__( param, Py.java2py( new java.net.URL( value ) ) ); } catch (MalformedURLException ex) { logger.log(Level.SEVERE, null, ex); } } else if ( type.equals('M') ) { paramsDictionary.__setitem__( param, Py.java2py( new java.io.File( value ) ) ); } else if ( type.equals('A') ) { value= org.autoplot.jythonsupport.Util.popString(value); paramsDictionary.__setitem__( param, Py.java2py( value ) ); } else if ( type.equals('R') ) { value= org.autoplot.jythonsupport.Util.popString(value); try { paramsDictionary.__setitem__( param, Py.java2py( new URI(value) ) ); } catch (URISyntaxException ex) { logger.log(Level.SEVERE, null, ex); } } else if ( type.equals('C') ) { value= org.autoplot.jythonsupport.Util.popString(value); paramsDictionary.__setitem__( param, Py.java2py( ColorUtil.decodeColor(value) ) ); } else { interp.exec( String.format("params['%s']=%s", param, value ) ); // TODO: nasty/clever code handles float vs int. } return; } } logger.log(Level.WARNING, "unable to find variable ''{0}''", param); } } /** * there's nasty code which looks for "val1:explaination of val" , and this * destroys URL values. Check to see if these look like URLs. * @param s * @return */ private static boolean appearsToBeUrl( String s ) { s= s.trim(); if ( s.startsWith("vap+") || s.startsWith("file:") || s.startsWith("http:") || s.startsWith("https:") || s.startsWith("ftp:") || s.startsWith("sftp:") ) { return true; } else { return false; } } /** * extract the data from the form into params. Note, strings and URIs are * quoted, not sure why. * @param fd form data containing GUI references * @param params map to contain the settings for each parameter, reading from the GUI. */ public static void resetVariables( FormData fd, Map params ) { for ( int j=0; j-1 ) { if ( appearsToBeUrl(value) ) { value= value.trim(); // Yeah kludge code, but for sure someone (me) is going to do this. --JF } else { switch (fd.typesList.get(j)) { case 'T': value= value.trim(); break; case 'A': //TODO: jupiter: 2029-02-02T00:00 to 2029-02-12T00:00 if ( fd.deftsList.get(j).contains(":") ) { // allow string to contain colon if default contains colon. value= value.trim(); } else { value= value.substring(0,i).trim(); } break; default: value= value.substring(0,i).trim(); break; } } } } else if ( jc instanceof JCheckBox ) { value= ((JCheckBox)jc).isSelected() ? "T" : "F"; } else { throw new IllegalArgumentException("the code needs attention: component for "+name+" not supported "); } String deft= fd.deftsList.get(j); char type= fd.typesList.get(j); if ( type=='F' ) { if ( value.equals("F") ) { value="False"; } else if (value.equals("T") ) { value="True"; } } if ( !value.equals(deft) || params.containsKey(name) || name.equals("timerange") ) { switch (type) { case 'A': value= value.replaceAll("\'", ""); if ( !( value.startsWith("'") && value.endsWith("'") ) ) { value= "'" + value + "'"; } params.put( name, value ); break; case 'R': case 'L': if ( !( value.startsWith("'") && value.endsWith("'") ) ) { value= "'" + value + "'"; } params.put( name, value ); break; default: params.put( name, value ); break; } } } } /** * Populates the JPanel with options. See org.autoplot.jythonsupport.ui.Util.createForm. * @param env environment variables such as PWD and dom. * @param f the file containing the script. * @param params map containing any settings for the variables. * @param paramsPanel JPanel to populate with the GUI items. (Can be null.) * @return the FormData from the initial view, since some clients will not show a GUI when there are no parameters. * @throws java.io.IOException when reading the file. */ public FormData doVariables( Map env, File f, Map params, final JPanel paramsPanel ) throws IOException { String src= FileUtil.readFileToString(f); return doVariables( env, src, params,paramsPanel); } /** * Repopulates the JPanel with options, to be used when the parameters can change the params that are read in. * @param env environment variables such as PWD and dom. * @param src the script loaded into a string. * @param params map containing any settings for the variables. * @param paramsPanel the GUI which needs to be revalidated. */ public void redoVariables( Map env, String src, Map params, final JPanel paramsPanel ) { paramsPanel.removeAll(); resetVariables( fd, params ); doVariables( env, src, params, paramsPanel ); paramsPanel.revalidate(); paramsPanel.repaint(); } /** * return spacer of width size. * @param size * @return */ private JComponent getSpacer( int size ) { JComponent spacer= new JLabel(" "); spacer.setSize( new Dimension(size,16) ); spacer.setMinimumSize( new Dimension(size,16) ); spacer.setPreferredSize( new Dimension(size,16) ); return spacer; } public FormData doVariables( final String src, Map params, final JPanel zparamsPanel ) { return doVariables( null, src, params, zparamsPanel ); } /** * Populates the JPanel with options. See org.autoplot.jythonsupport.ui.Util.createForm, this is only used * with the .jyds. TODO: Fix this!!! * * @param env null or an map containing variables like "dom" and "PWD" * @param src the script loaded into a string. * @param params map containing any settings for the variables. * @param zparamsPanel JPanel to populate with the GUI items. (Can be null.) * @see org.autoplot.jythonsupport.ui.Util#getParams(java.util.Map, java.lang.String, java.util.Map, org.das2.util.monitor.ProgressMonitor) * @return the FormData from the initial view, since some clients will not show a GUI when there are no parameters. */ public FormData doVariables( final Map env, final String src, Map params, final JPanel zparamsPanel ) { if ( params==null ) params= Collections.emptyMap(); this.params= new HashMap(params); logger.entering( "ParametersFormPanel", "doVariables", new Object[] { env, src, params } ); boolean hasVars; fd.tflist= new ArrayList(); fd.paramsList= new ArrayList(); fd.deftsList= new ArrayList(); fd.deftObjectList= new ArrayList(); fd.typesList= new ArrayList(); JScrollPane jp= new JScrollPane(); if ( zparamsPanel!=null ) zparamsPanel.add( jp ); final JPanel paramsPanel= new JPanel(); jp.getViewport().add(paramsPanel); paramsPanel.setLayout(new javax.swing.BoxLayout(paramsPanel, javax.swing.BoxLayout.Y_AXIS)); try { ScriptDescriptor sd= JythonUtil.describeScript( env, src, params ); List parms= sd.getParams(); //getParams( env, src, this.params, new NullProgressMonitor() ); boolean hasMeta= false; if ( sd.getTitle().length()>0 ) { paramsPanel.add( new JLabel("

"+sd.getTitle()+"

") ); hasMeta= true; } if ( sd.getDescription().length()>0 ) { Object opwd= env.get("PWD"); URL context= null; if ( opwd!=null ) { context= new URL( String.valueOf( opwd ) ); } paramsPanel.add( new JLinkyLabel( context,"
"+sd.getDescription()+"
") ); hasMeta= true; } if ( !hasMeta ) { paramsPanel.add( new JLabel("This script has the following input parameters. Buttons on the right show default values.

") ); } else { paramsPanel.add( new JLabel("

") ); } for ( Param parm : parms ) { String vname= parm.name; String label; if ( parm.enums!=null && parm.deft.getClass()!=parm.enums.get(0).getClass() ) { logger.warning("type of enumeration doesn't match default value."); } JComponent ctf; boolean hasLabels= parm.constraints!=null && parm.constraints.containsKey("labels"); boolean isBool= parm.enums!=null && isBoolean( parm.enums ) && !hasLabels; if ( parm.enums!=null ) { boolean okay=false; if ( !Py.java2py(parm.enums.get(0)).getClass().isAssignableFrom( Py.java2py(parm.deft).getClass() ) ) { logger.log(Level.WARNING, "parameter enumeration does not match type of default ("+parm.enums.get(0).getClass() +") for \"{0}\"", vname); } else { for ( Object o: parm.enums ) { if ( parm.deft.equals(o) ) okay=true; } if ( !okay ) logger.log(Level.WARNING, "parameter enumeration does not contain the default for \"{0}\"", vname); } } String colon= isBool ? "" : ":"; if ( parm.doc==null ) { label= vname+ colon; } else { String doc= parm.doc; if ( doc.startsWith("'") ) doc= doc.substring(1,doc.length()-1);// pop off the quotes if ( !parm.label.equals(parm.name) ) { doc= doc + " ("+parm.label+" inside the script)"; } if ( doc.length()>0 ) { label= "" + parm.name + ", " + doc + ""+colon+""; } else { label= "" + parm.name + colon+""; } } if ( !isBool ) { JPanel labelPanel= new JPanel(); labelPanel.setLayout( new BoxLayout( labelPanel, BoxLayout.X_AXIS ) ); JLabel l= new JLabel( label ); labelPanel.add( getSpacer() ); labelPanel.add( l ); labelPanel.setAlignmentX( JComponent.LEFT_ALIGNMENT ); paramsPanel.add( labelPanel ); } else { paramsPanel.add( Box.createVerticalStrut( paramsPanel.getFont().getSize() / 2 ) ); //TODO: verify. } JPanel valuePanel= new JPanel( ); valuePanel.setLayout( new BoxLayout( valuePanel, BoxLayout.X_AXIS ) ); if ( !isBool ) valuePanel.add( getSpacer() ); List values; if ( parm.examples!=null ) { values= parm.examples; } else { values= parm.enums; } // https://sourceforge.net/p/autoplot/feature-requests/796/ // little kludge: if the default is a timerange, then assume it must always be a timerange. char type= parm.type; if ( type=='S' ) { if ( UnitsUtil.isTimeLocation( ((DatumRange)parm.deft).getUnits() ) ) { type='T'; } } switch (type) { case 'U': { final DataSetSelector sel= new DataSetSelector(); sel.setPlotItButtonVisible(false); String val; if (params.get(vname)!=null ) { val= params.get(vname); if ( val.startsWith("'") ) val= val.substring(1); if ( val.endsWith("'") ) val= val.substring(0,val.length()-1); } else { val= String.valueOf( parm.deft ); params.put( vname, val ); } sel.setRecent( DataSetSelector.getDefaultRecent() ); sel.setValue( val ); valuePanel.add( getSpacer(7) ); // kludge. Set on Jeremy's home Ubuntu valuePanel.add( sel ); sel.setValue( val ); valuePanel.add( getSpacer(10) ); // put a little space in after the selector as well. ctf= sel; break; } case 'R': case 'L': { final DataSetSelector sel= new DataSetSelector(); sel.setPlotItButtonVisible(false); sel.setSuggestFiles(true); sel.setSuggestFsAgg(false); sel.setDisableDataSources(true); String surl= parm.deft.toString(); int i= surl.lastIndexOf("."); int j= surl.lastIndexOf("/"); if ( i>j ) { String ext= surl.substring(i); sel.setAcceptPattern(".*\\"+ext); } String val; if (params.get(vname)!=null ) { val= params.get(vname); if ( val.startsWith("'") ) val= val.substring(1); if ( val.endsWith("'") ) val= val.substring(0,val.length()-1); } else { val= String.valueOf( parm.deft ); params.put( vname, val ); } sel.setRecent( DataSetSelector.getDefaultRecent() ); sel.setValue( val ); valuePanel.add( getSpacer(7) ); // kludge. Set on Jeremy's home Ubuntu valuePanel.add( sel ); sel.setValue( val ); valuePanel.add( getSpacer(10) ); // put a little space in after the selector as well. ctf= sel; break; } case 'T': { String val; if ( params.get(vname)!=null ) { val= params.get(vname); if ( val.startsWith("'") ) val= val.substring(1); if ( val.endsWith("'") ) val= val.substring(0,val.length()-1); } else { val= String.valueOf( parm.deft ); params.put( vname, val ); } final RecentComboBox tcb= new RecentComboBox(); tcb.setPreferenceNode( RecentComboBox.PREF_NODE_TIMERANGE ); Dimension x= tcb.getPreferredSize(); x.width= Integer.MAX_VALUE; tcb.setMaximumSize(x); tcb.setAlignmentX( JComponent.LEFT_ALIGNMENT ); tcb.setText( val ); ctf= tcb; Icon fileIcon= new javax.swing.ImageIcon( Util.class.getResource("/org/autoplot/datasource/calendar.png")); JButton button= new JButton( fileIcon ); button.addActionListener((ActionEvent e) -> { TimeRangeTool tt= new TimeRangeTool(); tt.setSelectedRange(tcb.getSelectedItem().toString()); int r= WindowManager.showConfirmDialog( paramsPanel, tt, "Select Time Range", JOptionPane.OK_CANCEL_OPTION ); if ( r==JOptionPane.OK_OPTION) { tcb.setSelectedItem(tt.getSelectedRange()); } }); button.setToolTipText("Time Range Tool"); valuePanel.add( tcb ); button.setAlignmentX( JComponent.LEFT_ALIGNMENT ); valuePanel.add( button ); break; } case 'C': { String val; if ( params.get(vname)!=null ) { val= params.get(vname); if ( val.startsWith("'") ) val= val.substring(1); if ( val.endsWith("'") ) val= val.substring(0,val.length()-1); } else { val= ColorUtil.encodeColor( (Color)parm.deft ); params.put( vname, val ); } final JComponent fjcf; if ( values!=null && values.size()>0 ) { Object[] labels; if ( parm.examples!=null ) { labels= parm.examples.toArray(); } else { labels= values.toArray(); } if ( hasLabels ) { Object olabels= parm.constraints.get("labels"); if ( olabels instanceof List ) { List labelsList= (List)olabels; boolean useLabels= false; // only use labels if they add information. for ( int i=0; i-1 ) { jcb.setSelectedIndex(index); } else { jcb.setSelectedItem(val); } if ( !jcb.getSelectedItem().toString().startsWith( val ) ) { logger.fine("uh-oh."); } ctf= jcb; jcb.addActionListener((ActionEvent e) -> { redoVariables( env, src, ParametersFormPanel.this.params, zparamsPanel ); }); Dimension x= ctf.getPreferredSize(); x.width= Integer.MAX_VALUE; ctf.setMaximumSize(x); ctf.setAlignmentX( JComponent.LEFT_ALIGNMENT ); } else { final JTextField tf= new JTextField(); Dimension x= tf.getPreferredSize(); x.width= Integer.MAX_VALUE; tf.setMaximumSize(x); tf.setAlignmentX( JComponent.LEFT_ALIGNMENT ); tf.setText( val ); ctf= tf; } fjcf= ctf; valuePanel.add( ctf ); if ( values==null || values.isEmpty() ) { Icon fileIcon= new javax.swing.ImageIcon( Util.class.getResource("/org/autoplot/datasource/calendar.png")); JButton button= new JButton( fileIcon ); button.addActionListener((ActionEvent e) -> { Color c; String t; if ( fjcf instanceof JComboBox ) { t= (String)(((JComboBox)fjcf).getSelectedItem()); } else { t= ((JTextField)fjcf).getText(); } try { c= ColorUtil.decodeColor( t ); } catch ( IllegalArgumentException ex ) { c= Color.GRAY; } c = JColorChooser.showDialog( paramsPanel, "color", c ); if ( c!=null ) { if ( fjcf instanceof JComboBox ) { ((JComboBox)fjcf).setSelectedItem(ColorUtil.encodeColor(c)); } else { ((JTextField)fjcf).setText( ColorUtil.encodeColor(c) ); } } }); button.setToolTipText("Colorpicker"); button.setAlignmentX( JComponent.LEFT_ALIGNMENT ); valuePanel.add( button ); } break; } default: { String val; Object oval= params.get(vname); if ( oval!=null ) { if ( oval instanceof String ) { val= (String)oval; if ( val.startsWith("'") ) val= val.substring(1); if ( val.endsWith("'") ) val= val.substring(0,val.length()-1); } else { throw new IllegalArgumentException("param should be a string: "+vname); } } else { val= String.valueOf( parm.deft ); params.put( vname, val ); } if ( values!=null && values.size()>0 ) { if ( isBool ) { JCheckBox jcb= new JCheckBox( label ); jcb.setSelected( val.equals("T") || val.equals("1") || val.equals("True") ); jcb.addActionListener((ActionEvent e) -> { redoVariables( env, src, ParametersFormPanel.this.params, zparamsPanel ); }); ctf= jcb; } else { Object[] labels= values.toArray(); if ( hasLabels ) { Object olabels= parm.constraints.get("labels"); if ( olabels instanceof List ) { List labelsList= (List)olabels; boolean useLabels= false; // only use labels if they add information. for ( int i=0; i-1 ) { jcb.setSelectedIndex(index); } else { jcb.setSelectedItem(oval); } if ( parm.examples==null && !jcb.getSelectedItem().toString().startsWith(oval.toString()) ) { logger.fine("uh-oh."); } ctf= jcb; jcb.addActionListener((ActionEvent e) -> { redoVariables( env, src, ParametersFormPanel.this.params, zparamsPanel ); }); Dimension x= ctf.getPreferredSize(); x.width= Integer.MAX_VALUE; ctf.setMaximumSize(x); ctf.setAlignmentX( JComponent.LEFT_ALIGNMENT ); } } else { JTextField tf= new JTextField(); Dimension x= tf.getPreferredSize(); x.width= Integer.MAX_VALUE; tf.setMaximumSize(x); tf.setAlignmentX( JComponent.LEFT_ALIGNMENT ); tf.setText( val ); ctf= tf; } valuePanel.add( ctf ); Map c= parm.constraints; String stringType= String.valueOf( c.get("stringType") ); if ( type=='M' || stringType.equals("file") || stringType.equals("directory") ) { URL fileUrl= ParametersFormPanel.class.getResource("/resources/file.png"); ImageIcon icon= new ImageIcon(fileUrl); JButton b= new JButton(icon); final FileFilter filt; if ( c.containsKey("regex") ) { String regex= c.get("regex").toString(); Pattern p= Pattern.compile(regex); filt= new FileFilter() { @Override public boolean accept(File f) { return p.matcher(f.getPath()).matches(); } @Override public String getDescription() { if ( stringType.equals("file") ) { return "Files matching "+regex; } else { return "Directories matching "+regex; } } }; } else { filt=null; } b.addActionListener((ActionEvent e) -> { JFileChooser c1 = new JFileChooser(); if ( filt!=null ) c1.addChoosableFileFilter( filt ); if ( stringType.equals("directory") ) { c1.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); } c1.setSelectedFile(new File((((JTextField)ctf).getText()))); if (c1.showOpenDialog((JTextField)ctf) == JFileChooser.APPROVE_OPTION) { ((JTextField)ctf).setText(c1.getSelectedFile().toString()); } }); valuePanel.add( b ); } break; } } boolean shortLabel= ( parm.type=='R' || String.valueOf(parm.deft).length()>22 ) ; final String fdeft= shortLabel ? "default" : String.valueOf(parm.deft); final String fvalue= ( isBool && parm.deft instanceof Integer ) ? ( parm.deft.equals(0) ? "F" : "T" ) : String.valueOf(parm.deft); final JComponent ftf= ctf; JButton defaultButton= new JButton( new AbstractAction( isBool ? fvalue : fdeft ) { @Override public void actionPerformed( ActionEvent e ) { if ( ftf instanceof DataSetSelector ) { ((DataSetSelector)ftf).setValue(fvalue); } else if ( ftf instanceof JComboBox ) { JComboBox jcb= ((JComboBox)ftf); for ( int i=0; i0; if ( !hasVars ) { JLabel l= new JLabel("(no input parameters)"); l.setToolTipText("This looks through the code for getParam calls, and no conforming calls were found"); paramsPanel.add( l ); } paramsPanel.add( Box.createVerticalStrut( paramsPanel.getFont().getSize() * 2 ) ); paramsPanel.revalidate(); fd.count= fd.paramsList.size(); } catch (IOException ex) { logger.log(Level.SEVERE, ex.getMessage(), ex); } return fd; } /** * return the current state of the form data. * @return */ public FormData getFormData() { return this.fd; } }