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