c09d7290a00fad043ebdad9a6c35173e7cc37989
[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       Object newValue = updateSliderFromValueField();
578       boolean modified = true;
579       if (newValue.getClass() == lastVal.getClass())
580       {
581         modified = !newValue.equals(lastVal);
582       }
583       pmdialogbox.argSetModified(this, modified);
584     }
585
586     @Override
587     public int getBaseline(int width, int height)
588     {
589       return 0;
590     }
591
592     // from
593     // http://stackoverflow.com/questions/2743177/top-alignment-for-flowlayout
594     // helpful hint of using the Java 1.6 alignBaseLine property of FlowLayout
595     @Override
596     public Component.BaselineResizeBehavior getBaselineResizeBehavior()
597     {
598       return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
599     }
600
601     /**
602      * Answers an argument holding the value entered or selected in the dialog
603      * 
604      * @return
605      */
606     public ArgumentI getParameter()
607     {
608       ParameterI prm = parameter.copy();
609       if (isChoiceParameter)
610       {
611         String value = getSelectedValue(this.parameter, choicebox.getSelectedIndex());
612         prm.setValue(value);
613       }
614       else
615       {
616         prm.setValue(valueField.getText());
617       }
618       return prm;
619     }
620
621     public void init()
622     {
623       // reset the widget's initial value.
624       lastVal = null;
625     }
626
627     @Override
628     public void mouseClicked(MouseEvent e)
629     {
630       if (e.isPopupTrigger()) // for Windows
631       {
632         showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
633       }
634     }
635
636     @Override
637     public void mouseEntered(MouseEvent e)
638     {
639     }
640
641     @Override
642     public void mouseExited(MouseEvent e)
643     {
644     }
645
646     @Override
647     public void mousePressed(MouseEvent e)
648     {
649       if (e.isPopupTrigger()) // for Mac
650       {
651         showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
652       }
653     }
654
655     @Override
656     public void mouseReleased(MouseEvent e)
657     {
658     }
659
660     @Override
661     public void stateChanged(ChangeEvent e)
662     {
663       if (adjusting)
664       {
665         return;
666       }
667       try
668       {
669         adjusting = true;
670         if (!isLogarithmicParameter)
671         {
672           /*
673            * set (int or float formatted) text field value
674            */
675           valueField.setText(isIntegerParameter
676                   ? String.valueOf(slider.getValue())
677                   : formatDouble(
678                           slider.getValue() / (float) sliderScaleFactor));
679         }
680         else
681         {
682           double value = Math.pow(Math.E,
683                   slider.getValue() / (double) sliderScaleFactor);
684           valueField.setText(formatDouble(value));
685         }
686         checkIfModified();
687       } finally
688       {
689         adjusting = false;
690       }
691     }
692
693     /**
694      * Answers the value formatted as a string to 3 decimal places - in
695      * scientific notation if the value is less than 0.001
696      * 
697      * @param value
698      * @return
699      */
700     String formatDouble(double value)
701     {
702       String format = value < 0.001 ? "%3.1E" : "%3.3f";
703       return String.format(format, value);
704     }
705
706     /**
707      * Formats a number as integer or float (3dp) or scientific notation (1dp)
708      * 
709      * @param n
710      * @return
711      */
712     String formatNumber(Number n)
713     {
714       return n instanceof Integer ? String.valueOf(n.intValue())
715               : formatDouble(n.doubleValue());
716     }
717
718     void updateControls(ParameterI parm)
719     {
720       adjusting = true;
721       boolean init = (choicebox == null && valueField == null);
722       if (init)
723       {
724         if (isChoiceParameter)
725         {
726           choicebox = buildComboBox(parm);
727           choicebox.setSelectedItem(parm.getValue());
728           choicebox.addActionListener(this);
729           controlsPanel.add(choicebox, BorderLayout.CENTER);
730         }
731         else
732         {
733           slider = new JSlider();
734           slider.addChangeListener(this);
735           int cols = parm instanceof StringParameter ? 20 : 0;
736           valueField = new JTextField(cols);
737           valueField.addActionListener(this);
738           valueField.addKeyListener(new KeyAdapter()
739           {
740             @Override
741             public void keyReleased(KeyEvent e)
742             {
743               int keyCode = e.getKeyCode();
744               if (e.isActionKey() && keyCode != KeyEvent.VK_LEFT
745                       && keyCode != KeyEvent.VK_RIGHT)
746               {
747                 if (valueField.getText().trim().length() > 0)
748                 {
749                   actionPerformed(null);
750                 }
751               }
752             }
753           });
754           valueField.setPreferredSize(new Dimension(65, 25));
755           controlsPanel.add(slider, BorderLayout.WEST);
756           controlsPanel.add(valueField, BorderLayout.EAST);
757         }
758       }
759
760       if (!isChoiceParameter && parm != null)
761       {
762         valueField.setText(parm.getValue());
763       }
764       lastVal = updateSliderFromValueField();
765       adjusting = false;
766     }
767
768     /**
769      * Action depends on the type of the input parameter:
770      * <ul>
771      * <li>if a text input, returns the trimmed value</li>
772      * <li>if a choice list, returns the selected value</li>
773      * <li>if a value slider and input field, sets the value of the slider from
774      * the value in the text field, limiting it to any defined min-max
775      * range.</li>
776      * </ul>
777      * Answers the (possibly modified) input value, as a String, Integer, Float
778      * or Double.
779      * 
780      * @return
781      */
782     Object updateSliderFromValueField()
783     {
784       if (validator == null || isStringParameter)
785       {
786         if (isChoiceParameter)
787         {
788           return getSelectedValue(this.parameter, choicebox.getSelectedIndex());
789         }
790         slider.setVisible(false);
791         return valueField.getText().trim();
792       }
793
794       valueField.setText(valueField.getText().trim());
795
796       /*
797        * ensure not outside min-max range
798        * TODO: provide some visual indicator if limit reached
799        */
800       try
801       {
802         valueField.setBackground(Color.WHITE);
803         double d = Double.parseDouble(valueField.getText());
804         if (validator.getMin() != null
805                 && validator.getMin().doubleValue() > d)
806         {
807           valueField.setText(formatNumber(validator.getMin()));
808         }
809         if (validator.getMax() != null
810                 && validator.getMax().doubleValue() < d)
811         {
812           valueField.setText(formatNumber(validator.getMax()));
813         }
814       } catch (NumberFormatException e)
815       {
816         valueField.setBackground(Color.yellow);
817         return Float.NaN;
818       }
819
820       if (isIntegerParameter)
821       {
822         int iVal = 0;
823         try
824         {
825           iVal = Integer.valueOf(valueField.getText());
826         } catch (Exception e)
827         {
828           valueField.setBackground(Color.yellow);
829           return Integer.valueOf(0);
830         }
831
832         if (validator.getMin() != null && validator.getMax() != null)
833         {
834           slider.getModel().setRangeProperties(iVal, 1,
835                   validator.getMin().intValue(),
836                   validator.getMax().intValue() + 1, true);
837         }
838         else
839         {
840           slider.setVisible(false);
841         }
842         return new Integer(iVal);
843       }
844
845       if (isLogarithmicParameter)
846       {
847         double dVal = 0d;
848         try
849         {
850           double eValue = Double.valueOf(valueField.getText());
851           dVal = Math.log(eValue) * sliderScaleFactor;
852         } catch (Exception e)
853         {
854           // shouldn't be possible here
855           valueField.setBackground(Color.yellow);
856           return Double.NaN;
857         }
858         if (validator.getMin() != null && validator.getMax() != null)
859         {
860           double scaleMin = Math.log(validator.getMin().doubleValue())
861                   * sliderScaleFactor;
862           double scaleMax = Math.log(validator.getMax().doubleValue())
863                   * sliderScaleFactor;
864           slider.getModel().setRangeProperties((int) (dVal), 1,
865                   (int) scaleMin, 1 + (int) scaleMax, true);
866         }
867         else
868         {
869           slider.setVisible(false);
870         }
871         return new Double(dVal);
872       }
873
874       float fVal = 0f;
875       try
876       {
877         fVal = Float.valueOf(valueField.getText());
878       } catch (Exception e)
879       {
880         return Float.valueOf(0f); // shouldn't happen
881       }
882       if (validator.getMin() != null && validator.getMax() != null)
883       {
884         float scaleMin = validator.getMin().floatValue()
885                 * sliderScaleFactor;
886         float scaleMax = validator.getMax().floatValue()
887                 * sliderScaleFactor;
888         slider.getModel().setRangeProperties(
889                 (int) (fVal * sliderScaleFactor), 1, (int) scaleMin,
890                 1 + (int) scaleMax, true);
891       }
892       else
893       {
894         slider.setVisible(false);
895       }
896       return new Float(fVal);
897     }
898   }
899
900   /**
901    * Constructor with the option to show 'compact' format (parameter description
902    * as tooltip) or 'expanded' format (parameter description in a textbox which
903    * may be opened or closed). Use compact for simple description text, expanded
904    * for more wordy or formatted text.
905    * 
906    * @param paramContainer
907    */
908   public OptsAndParamsPage(OptsParametersContainerI paramContainer,
909           boolean compact)
910   {
911     poparent = paramContainer;
912     this.compact = compact;
913   }
914
915   public static void showUrlPopUp(JComponent invoker, final String finfo,
916           int x, int y)
917   {
918
919     JPopupMenu mnu = new JPopupMenu();
920     JMenuItem mitem = new JMenuItem(
921             MessageManager.formatMessage("label.view_params", new String[]
922             { finfo }));
923     mitem.addActionListener(new ActionListener()
924     {
925
926       @Override
927       public void actionPerformed(ActionEvent e)
928       {
929         Desktop.showUrl(finfo);
930
931       }
932     });
933     mnu.add(mitem);
934     mnu.show(invoker, x, y);
935   }
936
937   public Map<String, OptionBox> getOptSet()
938   {
939     return optSet;
940   }
941
942   public void setOptSet(Map<String, OptionBox> optSet)
943   {
944     this.optSet = optSet;
945   }
946
947   public Map<String, ParamBox> getParamSet()
948   {
949     return paramSet;
950   }
951
952   public void setParamSet(Map<String, ParamBox> paramSet)
953   {
954     this.paramSet = paramSet;
955   }
956
957   OptionBox addOption(OptionI opt)
958   {
959     OptionBox cb = optSet.get(opt.getName());
960     if (cb == null)
961     {
962       cb = new OptionBox(opt);
963       optSet.put(opt.getName(), cb);
964       // jobOptions.add(cb, FlowLayout.LEFT);
965     }
966     return cb;
967   }
968
969   ParamBox addParameter(ParameterI arg)
970   {
971     ParamBox pb = paramSet.get(arg.getName());
972     if (pb == null)
973     {
974       pb = new ParamBox(poparent, arg);
975       paramSet.put(arg.getName(), pb);
976       // paramList.add(pb);
977     }
978     pb.init();
979     // take the defaults from the parameter
980     pb.updateControls(arg);
981     return pb;
982   }
983
984   void selectOption(OptionI option, String string)
985   {
986     OptionBox cb = optSet.get(option.getName());
987     if (cb == null)
988     {
989       cb = addOption(option);
990     }
991     cb.enabled.setSelected(string != null); // initial state for an option.
992     if (string != null)
993     {
994       if (option.getPossibleValues().contains(string))
995       {
996         cb.val.setSelectedItem(string);
997       }
998       else
999       {
1000         throw new Error(String.format("Invalid value '%s' for option '%s'",
1001                 string, option.getName()));
1002       }
1003     }
1004     if (option.isRequired() && !cb.enabled.isSelected())
1005     {
1006       // TODO: indicate paramset is not valid.. option needs to be selected!
1007     }
1008     cb.setInitialValue();
1009   }
1010
1011   void setParameter(ParameterI arg)
1012   {
1013     ParamBox pb = paramSet.get(arg.getName());
1014     if (pb == null)
1015     {
1016       addParameter(arg);
1017     }
1018     else
1019     {
1020       pb.updateControls(arg);
1021     }
1022
1023   }
1024
1025   /**
1026    * Answers a list of arguments representing all the options and arguments
1027    * selected on the dialog, holding their chosen or input values. Optional
1028    * parameters which were not selected are not included.
1029    * 
1030    * @return
1031    */
1032   public List<ArgumentI> getCurrentSettings()
1033   {
1034     List<ArgumentI> argSet = new ArrayList<>();
1035     for (OptionBox opts : getOptSet().values())
1036     {
1037       ArgumentI opt = opts.getSelectedOption();
1038       if (opt != null)
1039       {
1040         argSet.add(opt);
1041       }
1042     }
1043     for (ParamBox parambox : getParamSet().values())
1044     {
1045       ArgumentI parm = parambox.getParameter();
1046       if (parm != null)
1047       {
1048         argSet.add(parm);
1049       }
1050     }
1051
1052     return argSet;
1053   }
1054
1055   /**
1056    * A helper method that constructs and returns a CombBox for choice of the
1057    * possible option values. If display names are provided, then these are added
1058    * as options, otherwise the actual values are added.
1059    * 
1060    * @param opt
1061    * @return
1062    */
1063   protected JComboBox<String> buildComboBox(OptionI opt)
1064   {
1065     JComboBox<String> cb = null;
1066     List<String> displayNames = opt.getDisplayNames();
1067     if (displayNames != null)
1068     {
1069       cb = JvSwingUtils.buildComboWithTooltips(displayNames,
1070               opt.getPossibleValues());
1071     }
1072     else
1073     {
1074       cb = new JComboBox<>();
1075       for (String v : opt.getPossibleValues())
1076       {
1077         cb.addItem(v);
1078       }
1079     }
1080     return cb;
1081   }
1082
1083   /*
1084    * Answers the value corresponding to the selected item in the choice combo
1085    * box. If display names were not provided, this is simply the selected
1086    * value. If display names were provided, it is the value corresponding to
1087    * the selected item index.
1088    * 
1089    * @return
1090    */
1091   protected static String getSelectedValue(OptionI opt, int sel)
1092   {
1093     List<String> possibleValues = opt.getPossibleValues();
1094     String value = null;
1095     if (possibleValues != null && possibleValues.size() == 1)
1096     {
1097       // Hack to make sure the default value for an enabled option with only
1098       // one value is actually returned even if this.val is not displayed
1099       value = possibleValues.get(0);
1100     }
1101     else
1102     {
1103       if (sel >= 0 && sel < possibleValues.size())
1104       {
1105         value = possibleValues.get(sel);
1106       }
1107       else
1108       {
1109         value = opt.getValue();
1110       }
1111     }
1112     return value;
1113   }
1114 }