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