b13d77d1363ea0481ef53d46fa8b793a61ae83d5
[jalview.git] / src / jalview / gui / OptsAndParamsPage.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import jalview.util.MessageManager;
24 import jalview.ws.jws2.dm.JabaOption;
25 import jalview.ws.params.ArgumentI;
26 import jalview.ws.params.OptionI;
27 import jalview.ws.params.ParameterI;
28 import jalview.ws.params.ValueConstrainI;
29 import jalview.ws.params.ValueConstrainI.ValueType;
30 import jalview.ws.params.simple.LogarithmicParameter;
31 import jalview.ws.params.simple.StringParameter;
32
33 import java.awt.BorderLayout;
34 import java.awt.Color;
35 import java.awt.Component;
36 import java.awt.Dimension;
37 import java.awt.FlowLayout;
38 import java.awt.Font;
39 import java.awt.Rectangle;
40 import java.awt.event.ActionEvent;
41 import java.awt.event.ActionListener;
42 import java.awt.event.KeyAdapter;
43 import java.awt.event.KeyEvent;
44 import java.awt.event.MouseEvent;
45 import java.awt.event.MouseListener;
46 import java.net.URL;
47 import java.util.ArrayList;
48 import java.util.LinkedHashMap;
49 import java.util.List;
50 import java.util.Map;
51
52 import javax.swing.JButton;
53 import javax.swing.JCheckBox;
54 import javax.swing.JComboBox;
55 import javax.swing.JComponent;
56 import javax.swing.JLabel;
57 import javax.swing.JMenuItem;
58 import javax.swing.JPanel;
59 import javax.swing.JPopupMenu;
60 import javax.swing.JScrollPane;
61 import javax.swing.JSlider;
62 import javax.swing.JTextArea;
63 import javax.swing.JTextField;
64 import javax.swing.border.TitledBorder;
65 import javax.swing.event.ChangeEvent;
66 import javax.swing.event.ChangeListener;
67
68 import net.miginfocom.swing.MigLayout;
69
70 /**
71  * GUI generator/manager for options and parameters. Originally abstracted from
72  * the WsJobParameters dialog box.
73  * 
74  * @author jprocter
75  * 
76  */
77 public class OptsAndParamsPage
78 {
79   public static final int PARAM_WIDTH = 340;
80
81   public static final int PARAM_HEIGHT = 150;
82
83   public static final int PARAM_CLOSEDHEIGHT = 80;
84
85   URL linkImageURL = getClass().getResource("/images/link.gif");
86
87   Map<String, OptionBox> optSet = new LinkedHashMap<>();
88
89   Map<String, ParamBox> paramSet = new LinkedHashMap<>();
90
91   /*
92    * compact or verbose style parameters
93    */
94   boolean compact = false;
95
96   OptsParametersContainerI poparent;
97
98   /**
99    * A class that models a panel rendering a single option (checkbox or choice
100    * list)
101    */
102   public class OptionBox extends JPanel
103           implements MouseListener, ActionListener
104   {
105     JCheckBox enabled;
106
107     final URL finfo;
108
109     boolean hasLink = false;
110
111     boolean initEnabled = false;
112
113     String initVal = null;
114
115     OptionI option;
116
117     JComboBox<String> val;
118
119     /**
120      * Constructs and adds labels and controls to the panel for one Option
121      * 
122      * @param opt
123      */
124     public OptionBox(OptionI opt)
125     {
126       option = opt;
127       setLayout(new FlowLayout(FlowLayout.LEFT));
128       enabled = new JCheckBox(opt.getName());
129       enabled.setSelected(opt.isRequired());
130
131       /*
132        * If option is required, show a label, if optional a checkbox
133        * (but not for Jabaws pending JWS-126 resolution)
134        */
135       if (opt.isRequired() && !(opt instanceof JabaOption))
136       {
137         finfo = null;
138         add(new JLabel(opt.getName()));
139       }
140       else
141       {
142         finfo = option.getFurtherDetails();
143         configureCheckbox(opt);
144         add(enabled);
145       }
146
147       /*
148        * construct the choice box with possible values, 
149        * or their display names if provided
150        */
151       List<String> displayNames = opt.getDisplayNames();
152       if (displayNames != null)
153       {
154         val = JvSwingUtils.buildComboWithTooltips(displayNames,
155                 opt.getPossibleValues());
156       }
157       else
158       {
159         val = new JComboBox<>();
160         for (String v : opt.getPossibleValues())
161         {
162           val.addItem(v);
163         }
164       }
165       val.setSelectedItem(opt.getValue());
166
167       /*
168        * only show the choicebox if there is more than one option,
169        * or the option is mandatory
170        */
171       if (opt.getPossibleValues().size() > 1 || opt.isRequired())
172       {
173         val.addActionListener(this);
174         add(val);
175       }
176
177       setInitialValue();
178     }
179
180     /**
181      * Configures the checkbox that controls whether or not the option is
182      * selected
183      * 
184      * @param opt
185      */
186     protected void configureCheckbox(OptionI opt)
187     {
188       enabled.setFont(new Font("Verdana", Font.PLAIN, 11));
189       enabled.addActionListener(this);
190       final String desc = opt.getDescription();
191       if (finfo != null)
192       {
193         hasLink = true;
194         String description = desc;
195         if (desc == null || desc.trim().isEmpty())
196         {
197           description = MessageManager
198                   .getString("label.opt_and_params_further_details");
199         }
200         description = description + "<br><img src=\"" + linkImageURL
201                 + "\"/>";
202         String text = JvSwingUtils.wrapTooltip(true, description);
203         enabled.setToolTipText(text);
204         enabled.addMouseListener(this); // for popup menu to show link
205       }
206       else
207       {
208         if (desc != null && desc.trim().length() > 0)
209         {
210           enabled.setToolTipText(JvSwingUtils.wrapTooltip(true, desc));
211         }
212       }
213     }
214
215     @Override
216     public void actionPerformed(ActionEvent e)
217     {
218       if (e.getSource() != enabled)
219       {
220         enabled.setSelected(true);
221       }
222       checkIfModified();
223     }
224
225     private void checkIfModified()
226     {
227       boolean notmod = (initEnabled == enabled.isSelected());
228       if (enabled.isSelected())
229       {
230         if (initVal != null)
231         {
232           notmod &= initVal.equals(val.getSelectedItem());
233         }
234         else
235         {
236           // compare against default service setting
237           notmod &= option.getValue() == null
238                   || option.getValue().equals(val.getSelectedItem());
239         }
240       }
241       else
242       {
243         notmod &= (initVal != null) ? initVal.equals(val.getSelectedItem())
244                 : val.getSelectedItem() != initVal;
245       }
246       poparent.argSetModified(this, !notmod);
247     }
248
249     /**
250      * Answers null if the option is not selected, else a new Option holding the
251      * selected value
252      * 
253      * @return
254      */
255     public OptionI getSelectedOption()
256     {
257       if (!enabled.isSelected())
258       {
259         return null;
260       }
261       String value = getSelectedValue();
262       OptionI opt = option.copy();
263       opt.setValue(value);
264       return opt;
265     }
266
267     /*
268      * Answers the value corresponding to the selected item in the choice combo
269      * box. If display names were not provided, this is simply the selected
270      * value. If display names were provided, it is the value corresponding to
271      * the selected item index.
272      * 
273      * @return
274      */
275     protected String getSelectedValue()
276     {
277       List<String> possibleValues = option.getPossibleValues();
278       String value = null;
279       if (possibleValues != null && possibleValues.size() == 1)
280       {
281         // Hack to make sure the default value for an enabled option with only
282         // one value is actually returned even if this.val is not displayed
283         value = possibleValues.get(0);
284       }
285       else
286       {
287         int sel = val.getSelectedIndex();
288         if (sel >= 0 && sel < possibleValues.size())
289         {
290           value = possibleValues.get(sel);
291         }
292         else
293         {
294           value = option.getValue();
295         }
296       }
297       return value;
298     }
299
300     @Override
301     public void mouseClicked(MouseEvent e)
302     {
303       if (e.isPopupTrigger()) // for Windows
304       {
305         showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
306       }
307     }
308
309     @Override
310     public void mouseEntered(MouseEvent e)
311     {
312     }
313
314     @Override
315     public void mouseExited(MouseEvent e)
316     {
317     }
318
319     @Override
320     public void mousePressed(MouseEvent e)
321     {
322       if (e.isPopupTrigger()) // Mac
323       {
324         showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
325       }
326     }
327
328     @Override
329     public void mouseReleased(MouseEvent e)
330     {
331     }
332
333     public void resetToDefault(boolean setDefaultParams)
334     {
335       enabled.setSelected(false);
336       if (option.isRequired()
337               || (setDefaultParams && option.getValue() != null))
338       {
339         // Apply default value
340         selectOption(option, option.getValue());
341       }
342     }
343
344     public void setInitialValue()
345     {
346       initEnabled = enabled.isSelected();
347       if (option.getPossibleValues() != null
348               && option.getPossibleValues().size() > 1)
349       {
350         initVal = (String) val.getSelectedItem();
351       }
352       else
353       {
354         initVal = (initEnabled) ? (String) val.getSelectedItem() : null;
355       }
356     }
357
358     /**
359      * toString representation for identification in the debugger only
360      */
361     @Override
362     public String toString()
363     {
364       return option == null ? super.toString() : option.toString();
365     }
366
367   }
368
369   /**
370    * A class that models a panel rendering a single parameter
371    */
372   public class ParamBox extends JPanel
373           implements ChangeListener, ActionListener, MouseListener
374   {
375     /*
376      * parameter values (or their logs) are multiplied by this
377      * scaling factor to ensure an integer range for the slider
378      */
379     private int sliderScaleFactor = 1;
380
381     boolean isLogarithmicParameter;
382
383     boolean isChoiceParameter;
384
385     boolean isIntegerParameter;
386
387     boolean isStringParameter;
388
389     boolean adjusting;
390
391     JComboBox<String> choicebox;
392
393     JPanel controlsPanel = new JPanel();
394
395     boolean descriptionIsVisible = false;
396
397     JScrollPane descPanel = new JScrollPane();
398
399     final URL finfo;
400
401     Object lastVal;
402
403     ParameterI parameter;
404
405     final OptsParametersContainerI pmdialogbox;
406
407     JPanel settingPanel = new JPanel();
408
409     JSlider slider;
410
411     JTextArea descriptionText = new JTextArea();
412
413     ValueConstrainI validator;
414
415     JTextField valueField;
416
417     private String descTooltip;
418
419     public ParamBox(final OptsParametersContainerI paramContainer,
420             ParameterI parm)
421     {
422       pmdialogbox = paramContainer;
423       finfo = parm.getFurtherDetails();
424       validator = parm.getValidValue();
425       parameter = parm;
426
427       isLogarithmicParameter = parm instanceof LogarithmicParameter;
428
429       if (validator != null)
430       {
431         ValueType type = validator.getType();
432         isIntegerParameter = type == ValueType.Integer;
433         isStringParameter = type == ValueType.String;
434
435         /*
436          * ensure slider has an integer range corresponding to
437          * the min-max range of the parameter
438          */
439         if (validator.getMin() != null && validator.getMax() != null
440         // && !isIntegerParameter
441                 && !isStringParameter)
442         {
443           double min = validator.getMin().doubleValue();
444           double max = validator.getMax().doubleValue();
445           if (isLogarithmicParameter)
446           {
447             min = Math.log(min);
448             max = Math.log(max);
449           }
450           sliderScaleFactor = (int) (1000000 / (max - min));
451           // todo scaleMin, scaleMax could also be final fields
452         }
453       }
454
455       List<String> possibleValues = parameter.getPossibleValues();
456       isChoiceParameter = possibleValues != null
457               && !possibleValues.isEmpty();
458
459       if (compact)
460       {
461         addCompactParameter(parm);
462       }
463       else
464       {
465         addExpandableParam(parm);
466       }
467     }
468
469     /**
470      * Adds a 'compact' format parameter, with any help text shown as a tooltip
471      * 
472      * @param parm
473      */
474     private void addCompactParameter(ParameterI parm)
475     {
476       setLayout(new MigLayout("", "[][grow]"));
477       String ttipText = null;
478
479       controlsPanel.setLayout(new BorderLayout());
480
481       if (parm.getDescription() != null
482               && parm.getDescription().trim().length() > 0)
483       {
484         ttipText = (JvSwingUtils.wrapTooltip(true,
485                 parm.getDescription() + (finfo != null ? "<br><img src=\""
486                         + linkImageURL + "\"/>"
487                         + MessageManager.getString(
488                                 "label.opt_and_params_further_details")
489                         : "")));
490       }
491
492       JvSwingUtils.addtoLayout(this, ttipText, new JLabel(parm.getName()),
493               controlsPanel, "");
494       updateControls(parm);
495       validate();
496     }
497
498     /**
499      * Adds an 'expanded' format parameter, with any help shown in a panel that
500      * may be shown or hidden
501      * 
502      * @param parm
503      */
504     private void addExpandableParam(ParameterI parm)
505     {
506       setPreferredSize(new Dimension(PARAM_WIDTH, PARAM_CLOSEDHEIGHT));
507       setBorder(new TitledBorder(parm.getName()));
508       setLayout(null);
509       descriptionText.setFont(new Font("Verdana", Font.PLAIN, 11));
510       descriptionText.setBackground(getBackground());
511
512       descriptionText.setEditable(false);
513       descPanel.getViewport().setView(descriptionText);
514
515       descPanel.setVisible(false);
516
517       JPanel firstrow = new JPanel();
518       firstrow.setLayout(null);
519       controlsPanel.setLayout(new BorderLayout());
520       controlsPanel.setBounds(new Rectangle(39, 10, PARAM_WIDTH - 70,
521               PARAM_CLOSEDHEIGHT - 50));
522       firstrow.add(controlsPanel);
523       firstrow.setBounds(new Rectangle(10, 20, PARAM_WIDTH - 30,
524               PARAM_CLOSEDHEIGHT - 30));
525
526       if (parm.getDescription() != null
527               && parm.getDescription().trim().length() > 0)
528       {
529         addExpandableHelp(firstrow, parm);
530       }
531       add(firstrow);
532       validator = parm.getValidValue();
533       parameter = parm;
534       if (validator != null)
535       {
536         isIntegerParameter = validator.getType() == ValueType.Integer;
537       }
538       else
539       {
540         if (parameter.getPossibleValues() != null)
541         {
542           isChoiceParameter = true;
543         }
544       }
545       updateControls(parm);
546       descPanel.setBounds(new Rectangle(10, PARAM_CLOSEDHEIGHT,
547               PARAM_WIDTH - 20, PARAM_HEIGHT - PARAM_CLOSEDHEIGHT - 5));
548       add(descPanel);
549       validate();
550     }
551
552     /**
553      * Adds a button which can be clicked to show or hide help text
554      * 
555      * @param container
556      * @param param
557      */
558     protected void addExpandableHelp(JPanel container, ParameterI param)
559     {
560       JButton showDescBtn = new JButton("+");
561       showDescBtn.setFont(new Font("Verdana", Font.PLAIN, 8));
562       if (finfo != null)
563       {
564         descTooltip = JvSwingUtils.wrapTooltip(true,
565                 MessageManager.formatMessage(
566                         "label.opt_and_params_show_brief_desc_image_link",
567                         new String[]
568                         { linkImageURL.toExternalForm() }));
569         showDescBtn.addMouseListener(this);
570       }
571       else
572       {
573         descTooltip = JvSwingUtils.wrapTooltip(true, MessageManager
574                 .getString("label.opt_and_params_show_brief_desc"));
575       }
576       showDescBtn.setToolTipText(descTooltip);
577       showDescBtn.addActionListener(new ActionListener()
578       {
579         @Override
580         public void actionPerformed(ActionEvent e)
581         {
582           descriptionIsVisible = !descriptionIsVisible;
583           showDescBtn.setText(descriptionIsVisible ? "-" : "+");
584           showDescBtn.setToolTipText(
585                   descriptionIsVisible ? null : descTooltip);
586           descPanel.setVisible(descriptionIsVisible);
587           descPanel.getVerticalScrollBar().setValue(0);
588           ParamBox.this.setPreferredSize(new Dimension(PARAM_WIDTH,
589                   (descriptionIsVisible) ? PARAM_HEIGHT
590                           : PARAM_CLOSEDHEIGHT));
591           ParamBox.this.validate();
592           pmdialogbox.refreshParamLayout();
593         }
594       });
595       descriptionText.setWrapStyleWord(true);
596       descriptionText.setLineWrap(true);
597       descriptionText.setColumns(32);
598       descriptionText.setText(param.getDescription());
599       showDescBtn.setBounds(new Rectangle(10, 10, 16, 16));
600       container.add(showDescBtn);
601     }
602
603     @Override
604     public void actionPerformed(ActionEvent e)
605     {
606       if (adjusting)
607       {
608         return;
609       }
610       if (!isChoiceParameter)
611       {
612         updateSliderFromValueField();
613       }
614       checkIfModified();
615     }
616
617     /**
618      * Checks whether the value of this parameter has been changed and notifies
619      * the parent page accordingly
620      */
621     private void checkIfModified()
622     {
623       if (!adjusting)
624       {
625         try
626         {
627           adjusting = true;
628           Object newValue = updateSliderFromValueField();
629           boolean modified = true;
630           if (newValue.getClass() == lastVal.getClass())
631           {
632             modified = !newValue.equals(lastVal);
633           }
634           pmdialogbox.argSetModified(this, modified);
635         } finally
636         {
637           adjusting = false;
638         }
639       }
640     }
641
642     @Override
643     public int getBaseline(int width, int height)
644     {
645       return 0;
646     }
647
648     // from
649     // http://stackoverflow.com/questions/2743177/top-alignment-for-flowlayout
650     // helpful hint of using the Java 1.6 alignBaseLine property of FlowLayout
651     @Override
652     public Component.BaselineResizeBehavior getBaselineResizeBehavior()
653     {
654       return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
655     }
656
657     public ParameterI getParameter()
658     {
659       ParameterI prm = parameter.copy();
660       if (isChoiceParameter)
661       {
662         String value = getChoice();
663         prm.setValue(value);
664       }
665       else
666       {
667         prm.setValue(valueField.getText());
668       }
669       return prm;
670     }
671
672     /**
673      * Answers the value corresponding to the selected item in the choice combo
674      * box. If display names were not provided, this is simply the selected
675      * value. If display names were provided, it is the value corresponding to
676      * the selected item index.
677      * 
678      * @return
679      */
680     protected String getChoice()
681     {
682       int sel = choicebox.getSelectedIndex();
683       if (sel < 0 || sel >= parameter.getPossibleValues().size())
684       {
685         return null;
686       }
687       return parameter.getPossibleValues().get(sel);
688     }
689
690     public void init()
691     {
692       // reset the widget's initial value.
693       lastVal = null;
694     }
695
696     @Override
697     public void mouseClicked(MouseEvent e)
698     {
699       if (e.isPopupTrigger()) // for Windows
700       {
701         showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
702       }
703     }
704
705     @Override
706     public void mouseEntered(MouseEvent e)
707     {
708     }
709
710     @Override
711     public void mouseExited(MouseEvent e)
712     {
713     }
714
715     @Override
716     public void mousePressed(MouseEvent e)
717     {
718       if (e.isPopupTrigger()) // for Mac
719       {
720         showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
721       }
722     }
723
724     @Override
725     public void mouseReleased(MouseEvent e)
726     {
727     }
728
729     @Override
730     public void stateChanged(ChangeEvent e)
731     {
732       if (adjusting)
733       {
734         return;
735       }
736       try
737       {
738         adjusting = true;
739         if (!isLogarithmicParameter)
740         {
741           /*
742            * set (int or float formatted) text field value
743            */
744           valueField.setText(isIntegerParameter
745                   ? String.valueOf(slider.getValue())
746                   : formatDouble(
747                           slider.getValue() / (float) sliderScaleFactor));
748         }
749         else
750         {
751           double value = Math.pow(Math.E,
752                   slider.getValue() / (double) sliderScaleFactor);
753           valueField.setText(formatDouble(value));
754         }
755         checkIfModified();
756       } finally
757       {
758         adjusting = false;
759       }
760     }
761
762     /**
763      * Answers the value formatted as a string to 3 decimal places - in
764      * scientific notation if the value is less than 0.001
765      * 
766      * @param value
767      * @return
768      */
769     String formatDouble(double value)
770     {
771       String format = value < 0.001 ? "%3.1E" : "%3.3f";
772       return String.format(format, value);
773     }
774
775     /**
776      * Formats a number as integer or float (3dp) or scientific notation (1dp)
777      * 
778      * @param n
779      * @return
780      */
781     String formatNumber(Number n)
782     {
783       return n instanceof Integer ? String.valueOf(n.intValue())
784               : formatDouble(n.doubleValue());
785     }
786
787     void updateControls(ParameterI parm)
788     {
789       adjusting = true;
790       boolean init = (choicebox == null && valueField == null);
791       List<String> displayNames = parm.getDisplayNames();
792       if (init)
793       {
794         if (isChoiceParameter)
795         {
796           if (displayNames != null)
797           {
798             choicebox = JvSwingUtils.buildComboWithTooltips(displayNames,
799                     parm.getPossibleValues());
800           }
801           else
802           {
803             choicebox = new JComboBox<>();
804             for (String val : parm.getPossibleValues())
805             {
806               choicebox.addItem(val);
807             }
808           }
809           choicebox.addActionListener(this);
810           controlsPanel.add(choicebox, BorderLayout.CENTER);
811         }
812         else
813         {
814           slider = new JSlider();
815           slider.addChangeListener(this);
816           int cols = parm instanceof StringParameter ? 20 : 0;
817           valueField = new JTextField(cols);
818           valueField.addActionListener(this);
819           valueField.addKeyListener(new KeyAdapter()
820           {
821             @Override
822             public void keyReleased(KeyEvent e)
823             {
824               int keyCode = e.getKeyCode();
825               if (e.isActionKey() && keyCode != KeyEvent.VK_LEFT
826                       && keyCode != KeyEvent.VK_RIGHT)
827               {
828                 if (valueField.getText().trim().length() > 0)
829                 {
830                   actionPerformed(null);
831                 }
832               }
833             }
834           });
835           valueField.setPreferredSize(new Dimension(65, 25));
836           controlsPanel.add(slider, BorderLayout.WEST);
837           controlsPanel.add(valueField, BorderLayout.EAST);
838         }
839       }
840
841       if (!isChoiceParameter && parm != null)
842       {
843         valueField.setText(parm.getValue());
844       }
845       lastVal = updateSliderFromValueField();
846       adjusting = false;
847     }
848
849     /**
850      * Action depends on the type of the input parameter:
851      * <ul>
852      * <li>if a text input, returns the trimmed value</li>
853      * <li>if a choice list, returns the selected value</li>
854      * <li>if a value slider and input field, sets the value of the slider from
855      * the value in the text field, limiting it to any defined min-max
856      * range.</li>
857      * </ul>
858      * Answers the (possibly modified) input value, as a String, Integer, Float
859      * or Double.
860      * 
861      * @return
862      */
863     Object updateSliderFromValueField()
864     {
865       if (validator == null || isStringParameter)
866       {
867         if (isChoiceParameter)
868         {
869           return getChoice();
870         }
871         slider.setVisible(false);
872         return valueField.getText().trim();
873       }
874
875       valueField.setText(valueField.getText().trim());
876
877       /*
878        * ensure not outside min-max range
879        * TODO: provide some visual indicator if limit reached
880        */
881       try
882       {
883         valueField.setBackground(Color.WHITE);
884         double d = Double.parseDouble(valueField.getText());
885         if (validator.getMin() != null
886                 && validator.getMin().doubleValue() > d)
887         {
888           valueField.setText(formatNumber(validator.getMin()));
889         }
890         if (validator.getMax() != null
891                 && validator.getMax().doubleValue() < d)
892         {
893           valueField.setText(formatNumber(validator.getMax()));
894         }
895       } catch (NumberFormatException e)
896       {
897         valueField.setBackground(Color.yellow);
898         return Float.NaN;
899       }
900
901       if (isIntegerParameter)
902       {
903         int iVal = 0;
904         try
905         {
906           iVal = Integer.valueOf(valueField.getText());
907         } catch (Exception e)
908         {
909           valueField.setBackground(Color.yellow);
910           return Integer.valueOf(0);
911         }
912
913         if (validator.getMin() != null && validator.getMax() != null)
914         {
915           slider.getModel().setRangeProperties(iVal, 1,
916                   validator.getMin().intValue(),
917                   validator.getMax().intValue() + 1, true);
918         }
919         else
920         {
921           slider.setVisible(false);
922         }
923         return new Integer(iVal);
924       }
925
926       if (isLogarithmicParameter)
927       {
928         double dVal = 0d;
929         try
930         {
931           double eValue = Double.valueOf(valueField.getText());
932           dVal = Math.log(eValue) * sliderScaleFactor;
933         } catch (Exception e)
934         {
935           // shouldn't be possible here
936           valueField.setBackground(Color.yellow);
937           return Double.NaN;
938         }
939         if (validator.getMin() != null && validator.getMax() != null)
940         {
941           double scaleMin = Math.log(validator.getMin().doubleValue())
942                   * sliderScaleFactor;
943           double scaleMax = Math.log(validator.getMax().doubleValue())
944                   * sliderScaleFactor;
945           slider.getModel().setRangeProperties((int) (dVal), 1,
946                   (int) scaleMin, 1 + (int) scaleMax, true);
947         }
948         else
949         {
950           slider.setVisible(false);
951         }
952         return new Double(dVal);
953       }
954
955       float fVal = 0f;
956       try
957       {
958         fVal = Float.valueOf(valueField.getText());
959       } catch (Exception e)
960       {
961         return Float.valueOf(0f); // shouldn't happen
962       }
963       if (validator.getMin() != null && validator.getMax() != null)
964       {
965         float scaleMin = validator.getMin().floatValue()
966                 * sliderScaleFactor;
967         float scaleMax = validator.getMax().floatValue()
968                 * sliderScaleFactor;
969         slider.getModel().setRangeProperties(
970                 (int) (fVal * sliderScaleFactor), 1, (int) scaleMin,
971                 1 + (int) scaleMax, true);
972       }
973       else
974       {
975         slider.setVisible(false);
976       }
977       return new Float(fVal);
978     }
979   }
980
981   /**
982    * Constructor with the option to show 'compact' format (parameter description
983    * as tooltip) or 'expanded' format (parameter description in a textbox which
984    * may be opened or closed). Use compact for simple description text, expanded
985    * for more wordy or formatted text.
986    * 
987    * @param paramContainer
988    */
989   public OptsAndParamsPage(OptsParametersContainerI paramContainer,
990           boolean compact)
991   {
992     poparent = paramContainer;
993     this.compact = compact;
994   }
995
996   public static void showUrlPopUp(JComponent invoker, final String finfo,
997           int x, int y)
998   {
999
1000     JPopupMenu mnu = new JPopupMenu();
1001     JMenuItem mitem = new JMenuItem(
1002             MessageManager.formatMessage("label.view_params", new String[]
1003             { finfo }));
1004     mitem.addActionListener(new ActionListener()
1005     {
1006
1007       @Override
1008       public void actionPerformed(ActionEvent e)
1009       {
1010         Desktop.showUrl(finfo);
1011
1012       }
1013     });
1014     mnu.add(mitem);
1015     mnu.show(invoker, x, y);
1016   }
1017
1018   public Map<String, OptionBox> getOptSet()
1019   {
1020     return optSet;
1021   }
1022
1023   public void setOptSet(Map<String, OptionBox> optSet)
1024   {
1025     this.optSet = optSet;
1026   }
1027
1028   public Map<String, ParamBox> getParamSet()
1029   {
1030     return paramSet;
1031   }
1032
1033   public void setParamSet(Map<String, ParamBox> paramSet)
1034   {
1035     this.paramSet = paramSet;
1036   }
1037
1038   OptionBox addOption(OptionI opt)
1039   {
1040     OptionBox cb = optSet.get(opt.getName());
1041     if (cb == null)
1042     {
1043       cb = new OptionBox(opt);
1044       optSet.put(opt.getName(), cb);
1045       // jobOptions.add(cb, FlowLayout.LEFT);
1046     }
1047     return cb;
1048   }
1049
1050   ParamBox addParameter(ParameterI arg)
1051   {
1052     ParamBox pb = paramSet.get(arg.getName());
1053     if (pb == null)
1054     {
1055       pb = new ParamBox(poparent, arg);
1056       paramSet.put(arg.getName(), pb);
1057       // paramList.add(pb);
1058     }
1059     pb.init();
1060     // take the defaults from the parameter
1061     pb.updateControls(arg);
1062     return pb;
1063   }
1064
1065   void selectOption(OptionI option, String string)
1066   {
1067     OptionBox cb = optSet.get(option.getName());
1068     if (cb == null)
1069     {
1070       cb = addOption(option);
1071     }
1072     cb.enabled.setSelected(string != null); // initial state for an option.
1073     if (string != null)
1074     {
1075       if (option.getPossibleValues().contains(string))
1076       {
1077         cb.val.setSelectedItem(string);
1078       }
1079       else
1080       {
1081         throw new Error(String.format("Invalid value '%s' for option '%s'",
1082                 string, option.getName()));
1083       }
1084     }
1085     if (option.isRequired() && !cb.enabled.isSelected())
1086     {
1087       // TODO: indicate paramset is not valid.. option needs to be selected!
1088     }
1089     cb.setInitialValue();
1090   }
1091
1092   void setParameter(ParameterI arg)
1093   {
1094     ParamBox pb = paramSet.get(arg.getName());
1095     if (pb == null)
1096     {
1097       addParameter(arg);
1098     }
1099     else
1100     {
1101       pb.updateControls(arg);
1102     }
1103
1104   }
1105
1106   /**
1107    * recover options and parameters from GUI
1108    * 
1109    * @return
1110    */
1111   public List<ArgumentI> getCurrentSettings()
1112   {
1113     List<ArgumentI> argSet = new ArrayList<>();
1114     for (OptionBox opts : getOptSet().values())
1115     {
1116       OptionI opt = opts.getSelectedOption();
1117       if (opt != null)
1118       {
1119         argSet.add(opt);
1120       }
1121     }
1122     for (ParamBox parambox : getParamSet().values())
1123     {
1124       ParameterI parm = parambox.getParameter();
1125       if (parm != null)
1126       {
1127         argSet.add(parm);
1128       }
1129     }
1130
1131     return argSet;
1132   }
1133 }