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