improved parameter set store/save/rename logic and debugged parameter/option display
[jalview.git] / src / jalview / gui / WsJobParameters.java
1 package jalview.gui;
2
3 import java.awt.BorderLayout;
4 import java.awt.Color;
5 import java.awt.Component;
6 import java.awt.Dimension;
7 import java.awt.FlowLayout;
8 import java.awt.Font;
9 import java.awt.GridLayout;
10 import java.awt.Rectangle;
11 import java.awt.event.ActionEvent;
12 import java.awt.event.ActionListener;
13 import java.awt.event.ComponentEvent;
14 import java.awt.event.ComponentListener;
15 import java.awt.event.ContainerEvent;
16 import java.awt.event.ContainerListener;
17 import java.awt.event.ItemEvent;
18 import java.awt.event.ItemListener;
19 import java.awt.event.KeyEvent;
20 import java.awt.event.KeyListener;
21 import java.awt.event.MouseEvent;
22 import java.awt.event.MouseListener;
23 import java.awt.event.WindowEvent;
24 import java.awt.event.WindowListener;
25 import java.awt.event.WindowStateListener;
26 import java.util.ArrayList;
27 import java.util.EventObject;
28 import java.util.HashSet;
29 import java.util.Hashtable;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Vector;
34
35 import javax.swing.JButton;
36 import javax.swing.JCheckBox;
37 import javax.swing.JComboBox;
38 import javax.swing.JDialog;
39 import javax.swing.JFrame;
40 import javax.swing.JLabel;
41 import javax.swing.JList;
42 import javax.swing.JOptionPane;
43 import javax.swing.JPanel;
44 import javax.swing.JScrollPane;
45 import javax.swing.JSlider;
46 import javax.swing.JSplitPane;
47 import javax.swing.JTable;
48 import javax.swing.JTextArea;
49 import javax.swing.JTextField;
50 import javax.swing.ListSelectionModel;
51 import javax.swing.SwingConstants;
52 import javax.swing.SwingUtilities;
53 import javax.swing.border.TitledBorder;
54 import javax.swing.event.CellEditorListener;
55 import javax.swing.event.ChangeEvent;
56 import javax.swing.event.ChangeListener;
57 import javax.swing.event.DocumentEvent;
58 import javax.swing.event.DocumentListener;
59 import javax.swing.table.*;
60
61 import compbio.metadata.Argument;
62 import compbio.metadata.Option;
63 import compbio.metadata.Parameter;
64 import compbio.metadata.Preset;
65 import compbio.metadata.PresetManager;
66 import compbio.metadata.RunnerConfig;
67 import compbio.metadata.ValueConstrain;
68 import compbio.metadata.WrongParameterException;
69 import compbio.metadata.ValueConstrain.Type;
70
71 import jalview.ws.jws2.Jws2Discoverer;
72 import jalview.ws.jws2.Jws2Discoverer.Jws2Instance;
73
74 /**
75  * job parameter editing/browsing dialog box. User can browse existing settings
76  * (user + presets + Defaults), and any changes to parameters creates a modified
77  * user parameter set. LOGIC: If the parameter set is modified, and its name is
78  * a valid, non-existant user parameter set, then a save button is shown. If the
79  * parameter set is modified and its name is a valid, extant user parameter set,
80  * then an update button is shown. If user parameter set's name is edited, and
81  * old name exists as a writable user parameter set, then rename button is
82  * shown. If current parameter set is associated with a user defined parameter
83  * set, then : if set is modifed, a 'revert' button is shown. if set is not
84  * modified, a 'delete' button is shown.
85  * 
86  * @author JimP
87  * 
88  */
89 public class WsJobParameters extends JPanel implements ItemListener,
90         ActionListener, DocumentListener
91 {
92   private static final String SVC_DEF = "Defaults"; // this is the null
93                                                     // parameter set as shown to
94                                                     // user
95
96   private static final int PARAM_WIDTH = 340, PARAM_HEIGHT = 150,
97           PARAM_CLOSEDHEIGHT = 80;
98
99   private static final int OPTSET_HEIGHT = 30;
100
101   JPanel SetNamePanel = new JPanel();
102
103   JPanel setDetails = new JPanel();
104
105   JSplitPane settingsPanel = new JSplitPane();
106
107   JSplitPane jobPanel = new JSplitPane();
108
109   JPanel jobOptions = new JPanel();
110
111   JScrollPane jobOptionsPane = new JScrollPane();
112
113   JPanel jobParameters = new JPanel();
114
115   JButton savmodified = new JButton();
116
117   JButton renmodified = new JButton();
118
119   JButton deletesetting = new JButton();
120
121   JButton revertsetting = new JButton();
122
123   JButton startjob = new JButton();
124
125   JButton canceljob = new JButton();
126
127   JComboBox setName = new JComboBox();
128
129   JTextArea setDescr = new JTextArea();
130
131   JScrollPane paramPane = new JScrollPane();
132
133   // JList paramList = new JList();
134   JPanel paramList = new JPanel();
135
136   RunnerConfig serviceOptions;
137
138   private BorderLayout jparamLayout;
139
140   WsJobParameters(Jws2Instance service)
141   {
142     this(service, null);
143   }
144
145   public WsJobParameters(Jws2Instance service, Preset p)
146   {
147     this(null, service, p, null);
148   }
149
150   /**
151    * 
152    * @param desktop
153    *          - if null, create new JFrame outside of desktop
154    * @param service
155    * @param p
156    */
157   public WsJobParameters(JFrame parent, Jws2Instance service, Preset p,
158           List<Argument> jobArgset)
159   {
160     super();
161     jbInit();
162     // argSetModified(false);
163     // populate parameter table
164     initForService(service, p, jobArgset);
165     // display in new JFrame attached to parent.
166     validate();
167   }
168
169   int response = -1;
170
171   JDialog frame = null;
172
173   public boolean showRunDialog()
174   {
175
176     frame = new JDialog(Desktop.instance, true);
177
178     frame.setTitle("Edit parameters for " + service.getActionText());
179     Rectangle deskr = Desktop.instance.getBounds();
180     frame.setBounds(new Rectangle((int) (deskr.getCenterX() - 240),
181             (int) (deskr.getCenterY() - 92), 380, 385));
182     frame.setContentPane(this);
183
184     frame.setVisible(true);
185
186     if (response > 0)
187     {
188       return true;
189     }
190     return false;
191   }
192
193   protected JButton makeButton(String label, String tooltip,
194           ActionListener action)
195   {
196     JButton button = new JButton();
197     button.setText(label);
198     button.setFont(new java.awt.Font("Verdana", Font.PLAIN, 10));
199     button.setForeground(Color.black);
200     button.setHorizontalAlignment(SwingConstants.CENTER);
201     button.setToolTipText(tooltip);
202     button.addActionListener(action);
203     return button;
204   }
205
206   private void jbInit()
207   {
208     savmodified = makeButton("Save", "Not implemented yet :) ",
209             new ActionListener()
210             {
211
212               public void actionPerformed(ActionEvent e)
213               {
214                 savModified_actionPerformed(e);
215               }
216             });
217     renmodified = makeButton("Rename", "Not implemented yet :) ",
218             new ActionListener()
219             {
220
221               public void actionPerformed(ActionEvent e)
222               {
223                 renModified_actionPerformed(e);
224               }
225             });
226     deletesetting = makeButton("Delete", "Not implemented yet :) ",
227             new ActionListener()
228             {
229
230               public void actionPerformed(ActionEvent e)
231               {
232                 deleteSetting_actionPerformed(e);
233               }
234             });
235     revertsetting = makeButton("Revert", "Undo changes to parameters.",
236             new ActionListener()
237             {
238
239               public void actionPerformed(ActionEvent e)
240               {
241                 revertSetting_actionPerformed(e);
242               }
243             });
244
245     startjob.setFont(new java.awt.Font("Verdana", Font.PLAIN, 10));
246     startjob.setText("Start");
247     startjob.setToolTipText("Start Job");
248     startjob.addActionListener(new ActionListener()
249     {
250       public void actionPerformed(ActionEvent e)
251       {
252         startjob_actionPerformed(e);
253       }
254     });
255     canceljob.setFont(new java.awt.Font("Verdana", Font.PLAIN, 10));
256     canceljob.setText("Cancel");
257     canceljob.setToolTipText("Cancel Job");
258     canceljob.addActionListener(new ActionListener()
259     {
260       public void actionPerformed(ActionEvent e)
261       {
262         canceljob_actionPerformed(e);
263       }
264     });
265
266     setDetails.setBorder(new TitledBorder("Details"));
267     setDetails.setLayout(new BorderLayout());
268     setDescr.setColumns(40);
269     setDescr.setWrapStyleWord(true);
270     setDescr.setLineWrap(true);
271     setDescr.setBackground(getBackground());
272     setDescr.setEditable(true);
273     setDescr.getDocument().addDocumentListener(this);
274     JScrollPane setDescrView = new JScrollPane();
275     // setDescrView.setPreferredSize(new Dimension(350, 200));
276     setDescrView.getViewport().setView(setDescr);
277     setName.setEditable(true);
278     setName.addItemListener(this);
279     setName.getEditor().addActionListener(this);
280     SetNamePanel.setLayout(new BorderLayout());
281     SetNamePanel.add(setName, BorderLayout.WEST);
282     // initial button visibility
283     deletesetting.setVisible(false);
284     revertsetting.setVisible(false);
285     renmodified.setVisible(false);
286     savmodified.setVisible(false);
287     JPanel setsavebuts = new JPanel();
288     setsavebuts.setLayout(new FlowLayout());
289     setsavebuts.add(revertsetting, BorderLayout.CENTER);
290     setsavebuts.add(renmodified, BorderLayout.CENTER);
291     setsavebuts.add(deletesetting, BorderLayout.CENTER);
292     setsavebuts.add(savmodified, BorderLayout.EAST);
293     SetNamePanel.add(setsavebuts,BorderLayout.EAST);
294     setDetails.add(setDescrView, BorderLayout.CENTER);
295     // setDetails.setPreferredSize(new Dimension(360, 100));
296     jobParameters.setBorder(new TitledBorder("Parameters"));
297     jobParameters.setLayout(jparamLayout = new BorderLayout());
298     paramPane.setPreferredSize(new Dimension(360, 300));
299     paramPane.getVerticalScrollBar().setUnitIncrement(20);
300     // paramPanel.setPreferredSize(new Dimension(360, 300));
301     // TODO: relayout buttons nicely
302     paramPane.getViewport().setView(paramList);
303     jobParameters.add(paramPane, BorderLayout.CENTER);
304     JPanel jobOptionsPanel = new JPanel();
305     jobOptionsPanel.setLayout(new BorderLayout());
306     jobOptionsPanel.setBorder(new TitledBorder("Options"));
307     jobOptionsPane.getViewport().setView(jobOptions);
308     jobOptionsPanel.add(jobOptionsPane, BorderLayout.CENTER);
309     settingsPanel.setLeftComponent(jobOptionsPanel);
310     settingsPanel.setRightComponent(jobParameters);
311     settingsPanel.setOrientation(JSplitPane.VERTICAL_SPLIT);
312     settingsPanel.setDividerLocation(0.4);
313
314     setLayout(new BorderLayout());
315     // setPreferredSize(new Dimension(400, 600));
316     // setSize(new Dimension(400, 600));
317     jobPanel.setLeftComponent(setDetails);
318     jobPanel.setRightComponent(settingsPanel);
319     jobPanel.setOrientation(JSplitPane.VERTICAL_SPLIT);
320     jobPanel.setDividerLocation(0.8);
321     add(SetNamePanel, BorderLayout.NORTH);
322     add(jobPanel, BorderLayout.CENTER);
323     JPanel dialogpanel = new JPanel();
324     dialogpanel.add(startjob);
325     dialogpanel.add(canceljob);
326     add(dialogpanel, BorderLayout.SOUTH);
327   }
328
329   protected void revertSetting_actionPerformed(ActionEvent e)
330   {
331     // TODO Auto-generated method stub
332
333   }
334
335   protected void deleteSetting_actionPerformed(ActionEvent e)
336   {
337     String setname = (String) setName.getSelectedItem();
338     int p=setName.getSelectedIndex();
339     if (_getUserPreset(setname)!=null)
340     {
341       _deleteUserPreset(setname);
342       
343     }
344     if (p>0 && p+1==setName.getItemCount())
345     {
346       p--;
347     }
348     setName.setSelectedIndex(p);
349     
350   }
351
352   protected void renModified_actionPerformed(ActionEvent e)
353   {
354     if (curSetName==null || _getUserPreset(lastSetName)==null) {
355       System.err.println("can't rename - names unchanged or original name not a preset.");
356       return;
357     }
358     _deleteUserPreset(lastSetName);
359     lastSetName=curSetName;
360     savModified_actionPerformed(e);
361     curSetName=null;
362     boolean setd = settingDialog;
363     settingDialog=true;
364     syncSetNamesWithStore();
365     settingDialog=setd;
366   }
367
368   protected void savModified_actionPerformed(ActionEvent e)
369   {
370     _storeUserPreset(lastSetName=(String) setName.getSelectedItem(), setDescr.getText(), getJobParams());
371     curSetName=null;
372     initArgSetModified(); // reset the modification state
373   }
374
375   protected void canceljob_actionPerformed(ActionEvent e)
376   {
377     response = 0;
378     if (frame != null)
379     {
380       frame.setVisible(false);
381     }
382   }
383
384   protected void startjob_actionPerformed(ActionEvent e)
385   {
386     response = 1;
387     if (frame != null)
388     {
389       frame.setVisible(false);
390     }
391   }
392
393   Jws2Instance service;
394
395   /**
396    * list of service presets in the gui
397    */
398   Hashtable servicePresets = null;
399
400   /**
401    * set if dialog is being set - so handlers will avoid spurious events
402    */
403   boolean settingDialog = false;
404
405   void initForService(Jws2Instance service, Preset p,
406           List<Argument> jobArgset)
407   {
408     settingDialog = true;
409     this.service = service;
410     // TODO: Recover window geometry prefs for this service
411     // jobPanel.setDividerLocation(proportionalLocation)
412     // settingsPanel.setDividerLocation(proportionalLocation)
413     Hashtable exnames = new Hashtable();
414     for (int i = 0, iSize = setName.getItemCount(); i < iSize; i++)
415     {
416       exnames.put((String) setName.getItemAt(i), setName.getItemAt(i));
417     }
418     // Add the default entry - if not present already.
419     if (!exnames.contains(SVC_DEF))
420     {
421       setName.addItem(SVC_DEF);
422       exnames.put(SVC_DEF, SVC_DEF);
423     }
424     serviceOptions = service.getRunnerConfig();
425     // add any presets not already added.
426     String curname = (p == null ? "" : p.getName());
427     PresetManager prman = service.getPresets();
428     servicePresets = new Hashtable();
429     if (prman != null)
430     {
431       List prList = service.getPresets().getPresets();
432       if (prList != null)
433       {
434         for (Object pr : prList)
435         {
436           servicePresets.put(((Preset) pr).getName(), "preset");
437           if (!exnames.contains(((Preset) pr).getName()))
438           {
439             setName.addItem(((Preset) pr).getName());
440           }
441         }
442       }
443     }
444     updateTable(p, jobArgset);
445     initArgSetModified();
446     settingDialog = false;
447
448   }
449
450   @SuppressWarnings("unchecked")
451   private void updateTable(Preset p, List<Argument> jobArgset)
452   {
453     List<Parameter> setargs = new ArrayList<Parameter>();
454     // populate table from default parameter set.
455     List<Argument> args = serviceOptions.getArguments();
456
457     // split to params and required arguments
458     {
459       for (Argument arg : args)
460       {
461         Argument myarg = (Argument) arg;
462         // Ideally, Argument would implement isRequired !
463         if (myarg instanceof Parameter)
464         {
465           Parameter parm = (Parameter) myarg;
466           addParameter(parm);
467         }
468         else
469         {
470           if (myarg instanceof Option)
471           {
472             Option opt = (Option) myarg;
473             addOption(opt).resetToDefault();
474           }
475           else
476           {
477             System.err.println("Ignoring unknown service argument type "
478                     + arg.getClass().getName());
479           }
480         }
481       }
482       args = null; // no more args to process.
483     }
484     if (p != null)
485     {
486       // initialise setname
487       setName.setSelectedItem(lastSetName = p.getName());
488       setDescr.setText(lastDescrText = p.getDescription());
489       // TODO - URL link
490       try
491       {
492         args = p.getArguments(serviceOptions);
493       } catch (Exception e)
494       {
495         e.printStackTrace();
496       }
497       // TODO: check if args should be unselected prior to resetting using the
498       // preset
499       setargs.clear();
500     }
501     else
502     {
503       if (lastParmSet == null)
504       {
505         // first call - so create a dummy name
506         setName.setSelectedItem(lastSetName = SVC_DEF);
507       }
508     }
509
510     if (jobArgset != null)
511     {
512       argSetModified(jobArgset, true);
513       args = jobArgset;
514     }
515     // get setargs from current object
516     if (args != null)
517     {
518       for (Argument arg : args)
519       {
520         if (arg instanceof Parameter)
521         {
522           setParameter((Parameter) arg);
523         }
524         else
525         {
526           if (arg instanceof Option)
527           {
528             System.out.println("Setting option " + arg.getName() + " with "
529                     + arg.getDefaultValue());
530             selectOption((Option) arg, arg.getDefaultValue());
531           }
532         }
533
534       }
535     }
536
537     jobOptions.setPreferredSize(new Dimension(PARAM_WIDTH, optSet.size()
538             * OPTSET_HEIGHT));
539     jobOptions.setLayout(new GridLayout(optSet.size(), 1));
540     refreshParamLayout();
541     paramPane.validate();
542     validate();
543   }
544
545   private boolean isModified()
546   {
547     return modifiedElements.size() > 0;
548   }
549
550   private Hashtable modifiedElements = new Hashtable();
551
552   /**
553    * reset gui and modification state settings
554    */
555   private void initArgSetModified()
556   {
557     curSetName = null;
558     modifiedElements.clear();
559     renmodified.setVisible(false);
560     savmodified.setVisible(false);
561
562   }
563
564   private void argSetModified(Object modifiedElement, boolean b)
565   {
566     if (settingDialog)
567     {
568       return;
569     }
570
571     if (!b)
572     {
573       modifiedElements.remove(modifiedElement);
574     }
575     else
576     {
577       modifiedElements.put(modifiedElement, modifiedElement);
578     }
579     // set mod status based on presence of elements in table
580     if (modifiedElements.size() > 0)
581     {
582       makeSetNameValid();
583       savmodified.setVisible(true);
584       revertsetting.setVisible(false);
585     }
586     else
587     {
588       revertsetting.setVisible(false);
589       deletesetting
590               .setVisible(!isServicePreset((String) setName
591                       .getSelectedItem())
592                       && _getUserPreset((String) setName.getSelectedItem()) != null);
593       savmodified.setVisible(false);
594     }
595     // special reveal if setName has been modified
596     if (modifiedElements.get(setName) != null)
597     {
598       if (curSetName != null && lastSetName != null
599               && !lastSetName.equals(curSetName))
600       {
601         renmodified.setVisible(!isServicePreset(lastSetName));
602       }
603     }
604     else
605     {
606       // setname isn't in modlist - so don't rename
607       renmodified.setVisible(false);
608     }
609     validate();
610   }
611
612   private boolean isServicePreset(String selectedItem)
613   {
614     return selectedItem.equals(SVC_DEF)
615             || servicePresets.containsKey(selectedItem);
616   }
617
618   /**
619    * check if the current set name is a valid set name for saving, if not, then
620    * fix it.
621    */
622   private void makeSetNameValid()
623   {
624     boolean stn = settingDialog;
625     boolean renamed = false;
626     settingDialog = true;
627     String nm = (String) setName.getSelectedItem();
628     // check if the name is reserved - if it is, rename it.
629     if (isServicePreset(nm))
630     {
631       nm = "User " + nm;
632       renamed = true;
633     }
634     // if ()
635     // if nm exists in user's preset store then savmodified will update an
636     // existing user defined preset
637     // if nm doesn't exist, then the button will create a new preset.
638
639     boolean makeupdate = false;
640     // sync the gui with the preset database
641     for (int i = 0, iS = setName.getItemCount(); i < iS; i++)
642     {
643       String snm = (String) setName.getItemAt(i);
644       if (snm.equals(nm))
645       {
646         makeupdate = true;
647         setName.setSelectedIndex(i);
648       }
649     }
650
651     if (_getUserPreset(nm) != null)
652     {
653       savmodified.setText("Update");
654     }
655     else
656     {
657       if (renamed)
658       {
659         setName.addItem(nm);
660         setName.setSelectedIndex(setName.getItemCount() - 1);
661       }
662       savmodified.setText("Save");
663     }
664     settingDialog = stn;
665   }
666
667   private void addParameter(Parameter parm)
668   {
669     ParamBox pb = paramSet.get(parm.getName());
670     if (pb == null)
671     {
672       pb = new ParamBox(this, parm);
673       paramSet.put(parm.getName(), pb);
674       paramList.add(pb);
675     }
676     pb.init();
677     // take the defaults from the parameter
678     pb.updateControls(parm);
679   }
680
681   private void setParameter(Parameter arg)
682   {
683     ParamBox pb = paramSet.get(arg.getName());
684     if (pb == null)
685     {
686       addParameter(arg);
687     }
688     else
689     {
690       pb.updateControls(arg);
691     }
692
693   }
694
695   private void selectOption(Option opt, String string)
696   {
697     OptionBox cb = optSet.get(opt.getName());
698     if (cb == null)
699     {
700       cb = addOption(opt);
701     }
702     cb.enabled.setSelected(true); // initial state for an option.
703     if (string != null)
704     {
705       if (opt.getPossibleValues().contains(string))
706       {
707         cb.val.setSelectedItem(string);
708       }
709       else
710       {
711         throw new Error("Invalid value " + string + " for option " + opt);
712       }
713
714     }
715     if (opt.isRequired() && !cb.enabled.isSelected())
716     {
717       // TODO: indicate paramset is not valid.. option needs to be selected!
718     }
719     cb.setInitialValue();
720   }
721
722   Map<String, ParamBox> paramSet = new Hashtable<String, ParamBox>();
723
724   public class ParamBox extends JPanel implements ChangeListener,
725           ActionListener
726   {
727     JButton showDesc = new JButton();
728
729     JTextArea string = new JTextArea();
730
731     JScrollPane descPanel = new JScrollPane();
732
733     JSlider slider = null;
734
735     JTextField valueField = null;
736
737     ValueConstrain validator = null;
738
739     JPanel settingPanel = new JPanel();
740
741     JPanel controlPanel = new JPanel();
742
743     boolean integ = false;
744
745     boolean choice = false;
746
747     boolean descisvisible = false;
748
749     final WsJobParameters pmdialogbox;
750
751     public ParamBox(final WsJobParameters pmlayout, Parameter parm)
752     {
753       pmdialogbox = pmlayout;
754       setPreferredSize(new Dimension(PARAM_WIDTH, PARAM_CLOSEDHEIGHT));
755       setBorder(new TitledBorder(parm.getName()));
756       setLayout(null);
757       showDesc.setFont(new Font("Verdana", Font.PLAIN, 6));
758       showDesc.setText("+");
759       string.setFont(new Font("Verdana", Font.PLAIN, 11));
760       string.setBackground(getBackground());
761       // string.setSize(new Dimension(PARAM_WIDTH, 80));
762       string.setEditable(false);
763       descPanel.getViewport().setView(string);
764       // descPanel.setLocation(2,17);
765       descPanel.setVisible(false);
766       // string.setMinimumSize(new Dimension(140,80));
767       // string.setMaximumSize(new Dimension(280,80));
768       final ParamBox me = this;
769       showDesc.addActionListener(new ActionListener()
770       {
771
772         @Override
773         public void actionPerformed(ActionEvent e)
774         {
775           descisvisible = !descisvisible;
776           descPanel.setVisible(descisvisible);
777           me.setPreferredSize(new Dimension(PARAM_WIDTH,
778                   (descisvisible) ? PARAM_HEIGHT : PARAM_CLOSEDHEIGHT));
779           me.validate();
780           pmlayout.refreshParamLayout();
781         }
782       });
783       string.setWrapStyleWord(true);
784       string.setLineWrap(true);
785       string.setColumns(32);
786       string.setText(parm.getDescription());
787       JPanel firstrow = new JPanel();
788       firstrow.setLayout(null);
789       controlPanel.setLayout(new BorderLayout());
790       controlPanel.setBounds(new Rectangle(39, 10, PARAM_WIDTH - 70,
791               PARAM_CLOSEDHEIGHT - 50));
792       showDesc.setBounds(new Rectangle(10, 10, 16, 16));
793       firstrow.add(showDesc);
794       firstrow.add(controlPanel);
795       firstrow.setBounds(new Rectangle(10, 20, PARAM_WIDTH - 30,
796               PARAM_CLOSEDHEIGHT - 30));
797       add(firstrow);
798       validator = parm.getValidValue();
799       parameter = parm;
800       if (validator != null)
801       {
802         integ = validator.getType() == Type.Integer;
803       }
804       else
805       {
806         if (parameter.getPossibleValues() != null)
807         {
808           choice = true;
809         }
810       }
811       updateControls(parm);
812       descPanel.setBounds(new Rectangle(10, PARAM_CLOSEDHEIGHT,
813               PARAM_WIDTH - 20, PARAM_HEIGHT - PARAM_CLOSEDHEIGHT - 5));
814       add(descPanel);
815       validate();
816     }
817
818     public void init()
819     {
820       // reset the widget's initial value.
821       lastVal = null;
822     }
823
824     boolean adjusting = false;
825
826     Parameter parameter;
827
828     JComboBox choicebox;
829
830     public int getBoxHeight()
831     {
832       return (descisvisible ? PARAM_HEIGHT : PARAM_CLOSEDHEIGHT);
833     }
834
835     public void updateControls(Parameter parm)
836     {
837       adjusting = true;
838       boolean init = (choicebox == null && valueField == null);
839       float fVal = 0f;
840       int iVal = 0;
841       if (init)
842       {
843         if (choice)
844         {
845           choicebox = new JComboBox();
846           choicebox.addActionListener(this);
847           controlPanel.add(choicebox, BorderLayout.CENTER);
848         }
849         else
850         {
851           slider = new JSlider();
852           slider.addChangeListener(this);
853           valueField = new JTextField();
854           valueField.addActionListener(this);
855           valueField.setPreferredSize(new Dimension(60, 25));
856           controlPanel.add(slider, BorderLayout.WEST);
857           controlPanel.add(valueField, BorderLayout.EAST);
858
859         }
860       }
861
862       if (parm != null)
863       {
864         if (choice)
865         {
866           if (init)
867           {
868             List vals = parm.getPossibleValues();
869             for (Object val : vals)
870             {
871               choicebox.addItem(val);
872             }
873           }
874
875           if (parm.getDefaultValue() != null)
876           {
877             choicebox.setSelectedItem(parm.getDefaultValue());
878           }
879         }
880         else
881         {
882           valueField.setText(parm.getDefaultValue());
883         }
884       }
885       lastVal = updateSliderFromValueField();
886       adjusting = false;
887     }
888
889     Object lastVal;
890
891     public Parameter getParameter()
892     {
893       try
894       {
895         if (choice)
896         {
897           parameter.setDefaultValue((String) choicebox.getSelectedItem());
898         }
899         else
900         {
901           parameter.setDefaultValue(valueField.getText());
902         }
903       } catch (WrongParameterException e)
904       {
905         e.printStackTrace();
906         return null;
907       }
908       return parameter;
909     }
910
911     public Object updateSliderFromValueField()
912     {
913       int iVal;
914       float fVal;
915       if (validator != null)
916       {
917         if (integ)
918         {
919           iVal = 0;
920           try
921           {
922             valueField.setText(valueField.getText().trim());
923             iVal = Integer.valueOf(valueField.getText());
924           } catch (Exception e)
925           {
926           }
927           ;
928           if (validator.getMin() != null && validator.getMax() != null)
929           {
930             slider.getModel().setRangeProperties(iVal, 1,
931                     validator.getMin().intValue(),
932                     validator.getMax().intValue(), true);
933           }
934           else
935           {
936             slider.setVisible(false);
937           }
938           return new int[]
939           { iVal };
940         }
941         else
942         {
943           fVal = 0f;
944           try
945           {
946             fVal = Float.valueOf(valueField.getText());
947           } catch (Exception e)
948           {
949           }
950           ;
951           if (validator.getMin() != null && validator.getMax() != null)
952           {
953             slider.getModel().setRangeProperties((int) fVal * 1000, 1,
954                     (int) validator.getMin().floatValue() * 1000,
955                     (int) validator.getMax().floatValue() * 1000, true);
956           }
957           else
958           {
959             slider.setVisible(false);
960           }
961           return new float[]
962           { fVal };
963         }
964       }
965       else
966       {
967         if (!choice)
968         {
969           slider.setVisible(false);
970           return new String[]
971           { valueField.getText().trim() };
972         }
973         else
974         {
975           return new String[]
976           { (String) choicebox.getSelectedItem() };
977         }
978       }
979
980     }
981
982     @Override
983     public void stateChanged(ChangeEvent e)
984     {
985       if (!adjusting)
986       {
987         valueField.setText(""
988                 + ((integ) ? ("" + (int) slider.getValue())
989                         : ("" + (float) (slider.getValue() / 1000f))));
990         checkIfModified();
991       }
992
993     }
994
995     @Override
996     public void actionPerformed(ActionEvent e)
997     {
998       if (adjusting)
999       {
1000         return;
1001       }
1002       if (!choice)
1003       {
1004         updateSliderFromValueField();
1005       }
1006       checkIfModified();
1007     }
1008
1009     private void checkIfModified()
1010     {
1011       Object cstate = updateSliderFromValueField();
1012       boolean notmod = false;
1013       if (cstate.getClass() == lastVal.getClass())
1014       {
1015         if (cstate instanceof int[])
1016         {
1017           notmod = (((int[]) cstate)[0] == ((int[]) lastVal)[0]);
1018         }
1019         else if (cstate instanceof float[])
1020         {
1021           notmod = (((float[]) cstate)[0] == ((float[]) lastVal)[0]);
1022         }
1023         else if (cstate instanceof String[])
1024         {
1025           notmod = (((String[]) cstate)[0].equals(((String[]) lastVal)[0]));
1026         }
1027       }
1028       pmdialogbox.argSetModified(this, !notmod);
1029     }
1030   }
1031
1032   Map<String, OptionBox> optSet = new Hashtable<String, OptionBox>();
1033
1034   public class OptionBox extends JPanel implements ActionListener
1035   {
1036     JComboBox val = new JComboBox();
1037
1038     JCheckBox enabled = new JCheckBox();
1039
1040     Option option;
1041
1042     public OptionBox(Option opt)
1043     {
1044       option = opt;
1045       setLayout(new BorderLayout());
1046       enabled.setSelected(opt.isRequired()); // TODO: lock required options
1047       enabled.setFont(new Font("Verdana", Font.PLAIN, 11));
1048       enabled.setText(opt.getName());
1049       enabled.setToolTipText(opt.getDescription());
1050       enabled.addActionListener(this);
1051       add(enabled, BorderLayout.NORTH);
1052       if (opt.getPossibleValues().size() > 1)
1053       {
1054         setLayout(new GridLayout(1, 2));
1055         for (Object str : opt.getPossibleValues())
1056         {
1057           val.addItem((String) str);
1058         }
1059         val.setSelectedItem((String) opt.getDefaultValue());
1060         val.addActionListener(this);
1061         add(val, BorderLayout.SOUTH);
1062       }
1063       // TODO: add actionListeners for popup (to open further info),
1064       // and to update list of parameters if an option is enabled
1065       // that takes a value.
1066       setInitialValue();
1067     }
1068
1069     public void resetToDefault()
1070     {
1071       enabled.setSelected(false);
1072       if (option.isRequired())
1073       {
1074         // Apply default value
1075         selectOption(option, option.getDefaultValue());
1076       }
1077     }
1078
1079     boolean initEnabled = false;
1080
1081     String initVal = null;
1082
1083     public void setInitialValue()
1084     {
1085       initEnabled = enabled.isSelected();
1086       if (option.getPossibleValues() != null
1087               && option.getPossibleValues().size() > 1)
1088       {
1089         initVal = (String) val.getSelectedItem();
1090       }
1091       else
1092       {
1093         initVal = null;
1094       }
1095     }
1096
1097     public Option getOptionIfEnabled()
1098     {
1099       if (!enabled.isSelected())
1100       {
1101         return null;
1102       }
1103       try
1104       {
1105         if (val.getSelectedItem() != null)
1106         {
1107           option.setDefaultValue((String) val.getSelectedItem());
1108         }
1109       } catch (WrongParameterException e)
1110       {
1111         e.printStackTrace();
1112         return null;
1113       }
1114       return option;
1115     }
1116
1117     @Override
1118     public void actionPerformed(ActionEvent e)
1119     {
1120       if (e.getSource() != enabled)
1121       {
1122         enabled.setSelected(true);
1123       }
1124       checkIfModified();
1125     }
1126
1127     private void checkIfModified()
1128     {
1129       boolean notmod = (initEnabled == enabled.isSelected());
1130       if (enabled.isSelected() && initVal != null)
1131       {
1132         notmod |= initVal.equals(val.getSelectedItem());
1133       }
1134       argSetModified(this, !notmod);
1135     }
1136   }
1137
1138   private OptionBox addOption(Option opt)
1139   {
1140     OptionBox cb = optSet.get(opt.getName());
1141     if (cb == null)
1142     {
1143       cb = new OptionBox(opt);
1144       optSet.put(opt.getName(), cb);
1145       jobOptions.add(cb);
1146     }
1147     return cb;
1148   }
1149
1150   protected void refreshParamLayout()
1151   {
1152     int s = 100;
1153     for (ParamBox pbox : paramSet.values())
1154     {
1155       s += pbox.getBoxHeight();
1156     }
1157     paramList.setPreferredSize(new Dimension(PARAM_WIDTH, s));
1158     paramList.setLayout(new FlowLayout());
1159     validate();
1160   }
1161
1162   /**
1163    * testing method - grab a service and parameter set and show the window
1164    * 
1165    * @param args
1166    */
1167   public static void main(String[] args)
1168   {
1169     jalview.ws.jws2.Jws2Discoverer disc = jalview.ws.jws2.Jws2Discoverer
1170             .getDiscoverer();
1171     int p = 0;
1172     if (args.length > 3)
1173     {
1174       Vector<String> services = new Vector<String>();
1175       services.addElement(args[p++]);
1176       Jws2Discoverer.setServiceUrls(services);
1177     }
1178     try
1179     {
1180       disc.run();
1181     } catch (Exception e)
1182     {
1183       System.err.println("Aborting. Problem discovering services.");
1184       e.printStackTrace();
1185       return;
1186     }
1187     Jws2Discoverer.Jws2Instance lastserv = null;
1188     for (Jws2Discoverer.Jws2Instance service : disc.getServices())
1189     {
1190       lastserv = service;
1191       if (p < args.length && service.serviceType.equalsIgnoreCase(args[p]))
1192       {
1193         break;
1194       }
1195     }
1196     if (lastserv != null)
1197     {
1198       List<Preset> prl = null;
1199       Preset pr = null;
1200       if (++p < args.length)
1201       {
1202         PresetManager prman = lastserv.getPresets();
1203         if (prman != null)
1204         {
1205           pr = prman.getPresetByName(args[p]);
1206           if (pr == null)
1207           {
1208             // just grab the last preset.
1209             prl = prman.getPresets();
1210           }
1211         }
1212       }
1213       Iterator<Preset> en = (prl == null) ? null : prl.iterator();
1214       while (true)
1215       {
1216         if (en != null)
1217         {
1218           if (!en.hasNext())
1219           {
1220             en = prl.iterator();
1221           }
1222           pr = en.next();
1223         }
1224         WsJobParameters pgui = new WsJobParameters(lastserv, pr);
1225         JFrame jf = new JFrame("Parameters for " + lastserv.getActionText());
1226         JPanel cont = new JPanel();
1227         // jf.setPreferredSize(new Dimension(600, 800));
1228         cont.add(pgui);
1229         jf.add(cont);
1230         final Thread thr = Thread.currentThread();
1231         jf.addWindowListener(new WindowListener()
1232         {
1233
1234           @Override
1235           public void windowActivated(WindowEvent e)
1236           {
1237             // TODO Auto-generated method stub
1238
1239           }
1240
1241           @Override
1242           public void windowClosed(WindowEvent e)
1243           {
1244           }
1245
1246           @Override
1247           public void windowClosing(WindowEvent e)
1248           {
1249             thr.interrupt();
1250
1251           }
1252
1253           @Override
1254           public void windowDeactivated(WindowEvent e)
1255           {
1256             // TODO Auto-generated method stub
1257
1258           }
1259
1260           @Override
1261           public void windowDeiconified(WindowEvent e)
1262           {
1263             // TODO Auto-generated method stub
1264
1265           }
1266
1267           @Override
1268           public void windowIconified(WindowEvent e)
1269           {
1270             // TODO Auto-generated method stub
1271
1272           }
1273
1274           @Override
1275           public void windowOpened(WindowEvent e)
1276           {
1277             // TODO Auto-generated method stub
1278
1279           }
1280
1281         });
1282         jf.setVisible(true);
1283         boolean inter = false;
1284         while (!inter)
1285         {
1286           try
1287           {
1288             Thread.sleep(10000);
1289           } catch (Exception e)
1290           {
1291             inter = true;
1292           }
1293           ;
1294         }
1295         jf.dispose();
1296       }
1297     }
1298   }
1299
1300   public List<Argument> getJobParams()
1301   {
1302     List<Argument> argSet = new ArrayList<Argument>();
1303     // recover options and parameters from GUI
1304     for (OptionBox opts : optSet.values())
1305     {
1306       Option opt = opts.getOptionIfEnabled();
1307       if (opt != null)
1308       {
1309         argSet.add(opt);
1310       }
1311     }
1312     for (ParamBox parambox : paramSet.values())
1313     {
1314       Parameter parm = parambox.getParameter();
1315       if (parm != null)
1316       {
1317         argSet.add(parm);
1318       }
1319     }
1320
1321     return argSet;
1322   }
1323
1324   String lastParmSet = null;
1325
1326   Hashtable<String, Object[]> editedParams = new Hashtable<String, Object[]>();
1327
1328   /**
1329    * store the given parameters in the user parameter set database.
1330    * 
1331    * @param storeSetName
1332    *          - lastParmSet
1333    * @param descr
1334    *          - setDescr.getText()
1335    * @param jobParams
1336    *          - getJobParams()
1337    */
1338   private void _storeUserPreset(String storeSetName, String descr,
1339           List<Argument> jobParams)
1340   {
1341     // this is a simple hash store.
1342     Object[] pset;
1343     editedParams.put(storeSetName, pset = new Object[3]);
1344     pset[0] = storeSetName;
1345     pset[1] = descr;
1346     pset[2] = jobParams;
1347   }
1348
1349   private Object[] _getUserPreset(String setName)
1350   {
1351     return editedParams.get(setName);
1352   }
1353
1354   /**
1355    * remove the given user preset from the preset stash
1356    * 
1357    * @param setName
1358    */
1359   private void _deleteUserPreset(String setName)
1360   {
1361     editedParams.remove(setName);
1362   }
1363
1364   private void syncSetNamesWithStore()
1365   {
1366     int n = 0;
1367     // remove any set names in the drop down menu that aren't either a reserved
1368     // setting, or a user defined or service preset.
1369     while (n < setName.getItemCount())
1370     {
1371       String item = (String) setName.getItemAt(n);
1372       if (!isServicePreset(item) && _getUserPreset(item) == null)
1373       {
1374         setName.removeItemAt(n);
1375       }
1376       else
1377       {
1378         n++;
1379       }
1380
1381     }
1382   }
1383
1384   private void reInitDialog(String nextPreset)
1385   {
1386     settingDialog = true;
1387     syncSetNamesWithStore();
1388     // updateTable(null,null);
1389     Object[] pset = _getUserPreset(nextPreset);
1390     if (pset != null)
1391     {
1392       setDescr.setText((String) pset[1]);
1393       updateTable(null, (List<Argument>) pset[2]);
1394       lastParmSet = nextPreset;
1395       validate();
1396     }
1397     else
1398     {
1399       setDescr.setText("");
1400       // must be a default preset from service
1401       Preset p = null;
1402       try
1403       {
1404         PresetManager prman = service.getPresets();
1405         if (prman != null)
1406         {
1407           p = prman.getPresetByName(nextPreset);
1408         }
1409       } catch (Exception ex)
1410       {
1411         ex.printStackTrace();
1412       }
1413       if (p != null)
1414       {
1415         updateTable(p, null);
1416         lastParmSet = nextPreset;
1417       }
1418       else
1419       {
1420         updateTable(null, null);
1421       }
1422     }
1423     initArgSetModified();
1424     validate();
1425     settingDialog = false;
1426
1427   }
1428
1429   String curSetName = null;
1430
1431   @Override
1432   public void itemStateChanged(ItemEvent e)
1433   {
1434     if (settingDialog)
1435     {
1436       // ignore event
1437       return;
1438     }
1439     if (e.getSource() == setName)
1440     {
1441       String setname = (String) setName.getSelectedItem();
1442       if (setname == null)
1443       {
1444         return;
1445       }
1446       if (curSetName == null || !setname.equals(curSetName))
1447       {
1448         if (isModified()
1449                 && javax.swing.JOptionPane.showConfirmDialog(this,
1450                         "Parameter set is modifed - save ?",
1451                         "Save changes ?",
1452                         javax.swing.JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION)
1453         {
1454           savModified_actionPerformed(null);
1455         }
1456         reInitDialog(setname);
1457       }
1458     }
1459   }
1460
1461   /**
1462    * last saved name for this user preset
1463    */
1464   String lastSetName = null;
1465
1466   /**
1467    * last saved value of the description text for this user preset
1468    */
1469   String lastDescrText = null;
1470
1471   @Override
1472   public void actionPerformed(ActionEvent e)
1473   {
1474     if (e.getSource() instanceof Component)
1475     {
1476       Component src = (Component) e.getSource();
1477       if (src.getParent() == setName)
1478       {
1479         // rename any existing records we know about for this set.
1480         String newname = (String) e.getActionCommand();
1481         String msg=null;
1482         if (isServicePreset(newname)) {
1483           JOptionPane.showConfirmDialog(this, "Invalid name - preset already exists.", "Invalid name", JOptionPane.OK_OPTION);
1484           return;
1485         }
1486         curSetName = newname;
1487         System.err.println("Command " + curSetName + " : "
1488                 + setName.getSelectedItem());
1489         if (curSetName.trim().equals(setName.getSelectedItem()))
1490         {
1491           curSetName = null;
1492         }
1493         if (curSetName != null)
1494         {
1495           setName.addItem(curSetName);
1496           setName.setSelectedItem(curSetName);
1497           argSetModified(setName,
1498                   lastSetName != null && !curSetName.equals(lastSetName));
1499           return;
1500         }
1501
1502       }
1503     }
1504   }
1505
1506   private void checkDescrModified()
1507   {
1508     if (!settingDialog)
1509     {
1510
1511       argSetModified(
1512               setDescr,
1513               (lastDescrText == null ? setDescr.getText().trim().length() > 0
1514                       : !setDescr.getText().equals(lastDescrText)));
1515
1516     }
1517   }
1518
1519   @Override
1520   public void insertUpdate(DocumentEvent e)
1521   {
1522     checkDescrModified();
1523   }
1524
1525   @Override
1526   public void removeUpdate(DocumentEvent e)
1527   {
1528     checkDescrModified();
1529   }
1530
1531   @Override
1532   public void changedUpdate(DocumentEvent e)
1533   {
1534     checkDescrModified();
1535   }
1536 }