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