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