JAL-2985 show label instead of checkbox for a required (Jalview) Option
[jalview.git] / src / jalview / gui / OptsAndParamsPage.java
index d5da323..1843f49 100644 (file)
@@ -1,43 +1,51 @@
 /*
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8)
- * Copyright (C) 2012 J Procter, AM Waterhouse, LM Lui, J Engelhardt, G Barton, M Clamp, S Searle
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
  * 
  * This file is part of Jalview.
  * 
  * Jalview is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License 
- * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
  *  
  * Jalview is distributed in the hope that it will be useful, but 
  * WITHOUT ANY WARRANTY; without even the implied warranty 
  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
  * PURPOSE.  See the GNU General Public License for more details.
  * 
- * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
  */
 package jalview.gui;
 
+import jalview.util.MessageManager;
+import jalview.ws.jws2.dm.JabaOption;
 import jalview.ws.params.ArgumentI;
 import jalview.ws.params.OptionI;
 import jalview.ws.params.ParameterI;
 import jalview.ws.params.ValueConstrainI;
 import jalview.ws.params.ValueConstrainI.ValueType;
+import jalview.ws.params.simple.LogarithmicParameter;
+import jalview.ws.params.simple.StringParameter;
 
 import java.awt.BorderLayout;
+import java.awt.Color;
 import java.awt.Component;
 import java.awt.Dimension;
+import java.awt.FlowLayout;
 import java.awt.Font;
-import java.awt.GridLayout;
 import java.awt.Rectangle;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
 import java.awt.event.KeyEvent;
-import java.awt.event.KeyListener;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.net.URL;
 import java.util.ArrayList;
-import java.util.Hashtable;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -68,15 +76,33 @@ import net.miginfocom.swing.MigLayout;
  */
 public class OptsAndParamsPage
 {
-  /**
+  public static final int PARAM_WIDTH = 340;
+
+  public static final int PARAM_HEIGHT = 150;
+
+  public static final int PARAM_CLOSEDHEIGHT = 80;
+
+  URL linkImageURL = getClass().getResource("/images/link.gif");
+
+  Map<String, OptionBox> optSet = new LinkedHashMap<>();
+
+  Map<String, ParamBox> paramSet = new LinkedHashMap<>();
+
+  /*
    * compact or verbose style parameters
    */
   boolean compact = false;
 
-  public class OptionBox extends JPanel implements MouseListener,
-          ActionListener
+  OptsParametersContainerI poparent;
+
+  /**
+   * A class that models a panel rendering a single option (checkbox or choice
+   * list)
+   */
+  public class OptionBox extends JPanel
+          implements MouseListener, ActionListener
   {
-    JCheckBox enabled = new JCheckBox();
+    JCheckBox enabled;
 
     final URL finfo;
 
@@ -88,60 +114,87 @@ public class OptsAndParamsPage
 
     OptionI option;
 
-    JLabel optlabel = new JLabel();
-
-    JComboBox val = new JComboBox();
+    JComboBox<String> val;
 
+    /**
+     * Constructs and adds labels and controls to the panel for one Option
+     * 
+     * @param opt
+     */
     public OptionBox(OptionI opt)
     {
       option = opt;
-      setLayout(new BorderLayout());
-      enabled.setSelected(opt.isRequired()); // TODO: lock required options
-      enabled.setFont(new Font("Verdana", Font.PLAIN, 11));
-      enabled.setText("");
-      enabled.setText(opt.getName());
-      enabled.addActionListener(this);
-      finfo = option.getFurtherDetails();
-      String desc = opt.getDescription();
-      if (finfo != null)
+      setLayout(new FlowLayout(FlowLayout.LEFT));
+      enabled = new JCheckBox(opt.getName());
+      enabled.setSelected(opt.isRequired());
+
+      /*
+       * If option is required, show a label, if optional a checkbox
+       * (but not for Jabaws pending JWS-126 resolution)
+       */
+      if (opt.isRequired() && !(opt instanceof JabaOption))
       {
-        hasLink = true;
-
-        enabled.setToolTipText("<html>"
-                + JvSwingUtils
-                        .wrapTooltip(((desc == null || desc.trim().length()==0) ? "see further details by right-clicking"
-                                : desc)
-                                + "<br><img src=\"" + linkImageURL + "\"/>")
-                + "</html>");
-        enabled.addMouseListener(this);
+        finfo = null;
+        add(new JLabel(opt.getName()));
       }
       else
       {
-        if (desc != null && desc.trim().length()>0)
-        {
-          enabled.setToolTipText("<html>"
-                  + JvSwingUtils.wrapTooltip(opt.getDescription())
-                  + "</html>");
-        }
+        finfo = option.getFurtherDetails();
+        configureCheckbox(opt);
+        add(enabled);
       }
-      add(enabled, BorderLayout.NORTH);
-      for (Object str : opt.getPossibleValues())
+
+      val = new JComboBox<>();
+      for (String str : opt.getPossibleValues())
       {
-        val.addItem((String) str);
+        val.addItem(str);
       }
-      val.setSelectedItem((String) opt.getValue());
-      if (opt.getPossibleValues().size() > 1)
+      val.setSelectedItem(opt.getValue());
+      if (opt.getPossibleValues().size() > 1 || opt.isRequired())
       {
-        setLayout(new GridLayout(1, 2));
         val.addActionListener(this);
-        add(val, BorderLayout.SOUTH);
+        add(val);
       }
-      // TODO: add actionListeners for popup (to open further info),
-      // and to update list of parameters if an option is enabled
-      // that takes a value. JBPNote: is this TODO still valid ?
+
       setInitialValue();
     }
 
+    /**
+     * Configures the checkbox that controls whether or not the option is
+     * selected
+     * 
+     * @param opt
+     */
+    protected void configureCheckbox(OptionI opt)
+    {
+      enabled.setFont(new Font("Verdana", Font.PLAIN, 11));
+      enabled.addActionListener(this);
+      final String desc = opt.getDescription();
+      if (finfo != null)
+      {
+        hasLink = true;
+        String description = desc;
+        if (desc == null || desc.trim().isEmpty())
+        {
+          description = MessageManager
+                  .getString("label.opt_and_params_further_details");
+        }
+        description = description + "<br><img src=\"" + linkImageURL
+                + "\"/>";
+        String text = JvSwingUtils.wrapTooltip(true, description);
+        enabled.setToolTipText(text);
+        enabled.addMouseListener(this); // for popup menu to show link
+      }
+      else
+      {
+        if (desc != null && desc.trim().length() > 0)
+        {
+          enabled.setToolTipText(JvSwingUtils.wrapTooltip(true, desc));
+        }
+      }
+    }
+
+    @Override
     public void actionPerformed(ActionEvent e)
     {
       if (e.getSource() != enabled)
@@ -203,42 +256,44 @@ public class OptsAndParamsPage
       return opt;
     }
 
+    @Override
     public void mouseClicked(MouseEvent e)
     {
-      if (javax.swing.SwingUtilities.isRightMouseButton(e))
+      if (e.isPopupTrigger()) // for Windows
       {
         showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
       }
     }
 
+    @Override
     public void mouseEntered(MouseEvent e)
     {
-      // TODO Auto-generated method stub
-
     }
 
+    @Override
     public void mouseExited(MouseEvent e)
     {
-      // TODO Auto-generated method stub
-
     }
 
+    @Override
     public void mousePressed(MouseEvent e)
     {
-      // TODO Auto-generated method stub
-
+      if (e.isPopupTrigger()) // Mac
+      {
+        showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
+      }
     }
 
+    @Override
     public void mouseReleased(MouseEvent e)
     {
-      // TODO Auto-generated method stub
-
     }
 
-    public void resetToDefault()
+    public void resetToDefault(boolean setDefaultParams)
     {
       enabled.setSelected(false);
-      if (option.isRequired())
+      if (option.isRequired()
+              || (setDefaultParams && option.getValue() != null))
       {
         // Apply default value
         selectOption(option, option.getValue());
@@ -259,27 +314,49 @@ public class OptsAndParamsPage
       }
     }
 
+    /**
+     * toString representation for identification in the debugger only
+     */
+    @Override
+    public String toString()
+    {
+      return option == null ? super.toString() : option.toString();
+    }
+
   }
 
-  public class ParamBox extends JPanel implements ChangeListener,
-          ActionListener, MouseListener
+  /**
+   * A class that models a panel rendering a single parameter
+   */
+  public class ParamBox extends JPanel
+          implements ChangeListener, ActionListener, MouseListener
   {
-    boolean adjusting = false;
+    /*
+     * parameter values (or their logs) are multiplied by this
+     * scaling factor to ensure an integer range for the slider
+     */
+    private int sliderScaleFactor = 1;
+
+    boolean isLogarithmicParameter;
+
+    boolean isChoiceParameter;
+
+    boolean isIntegerParameter;
+
+    boolean isStringParameter;
 
-    boolean choice = false;
+    boolean adjusting;
 
-    JComboBox choicebox;
+    JComboBox<String> choicebox;
 
-    JPanel controlPanel = new JPanel();
+    JPanel controlsPanel = new JPanel();
 
-    boolean descisvisible = false;
+    boolean descriptionIsVisible = false;
 
     JScrollPane descPanel = new JScrollPane();
 
     final URL finfo;
 
-    boolean integ = false;
-
     Object lastVal;
 
     ParameterI parameter;
@@ -288,152 +365,140 @@ public class OptsAndParamsPage
 
     JPanel settingPanel = new JPanel();
 
-    JButton showDesc = new JButton();
+    JSlider slider;
 
-    JSlider slider = null;
+    JTextArea descriptionText = new JTextArea();
 
-    JTextArea string = new JTextArea();
+    ValueConstrainI validator;
 
-    ValueConstrainI validator = null;
+    JTextField valueField;
 
-    JTextField valueField = null;
+    private String descTooltip;
 
-    public ParamBox(final OptsParametersContainerI pmlayout, ParameterI parm)
+    public ParamBox(final OptsParametersContainerI paramContainer,
+            ParameterI parm)
     {
-      pmdialogbox = pmlayout;
+      pmdialogbox = paramContainer;
       finfo = parm.getFurtherDetails();
       validator = parm.getValidValue();
       parameter = parm;
+
+      isLogarithmicParameter = parm instanceof LogarithmicParameter;
+
       if (validator != null)
       {
-        integ = validator.getType() == ValueType.Integer;
-      }
-      else
-      {
-        if (parameter.getPossibleValues() != null)
+        ValueType type = validator.getType();
+        isIntegerParameter = type == ValueType.Integer;
+        isStringParameter = type == ValueType.String;
+
+        /*
+         * ensure slider has an integer range corresponding to
+         * the min-max range of the parameter
+         */
+        if (validator.getMin() != null && validator.getMax() != null
+        // && !isIntegerParameter
+                && !isStringParameter)
         {
-          choice = true;
+          double min = validator.getMin().doubleValue();
+          double max = validator.getMax().doubleValue();
+          if (isLogarithmicParameter)
+          {
+            min = Math.log(min);
+            max = Math.log(max);
+          }
+          sliderScaleFactor = (int) (1000000 / (max - min));
+          // todo scaleMin, scaleMax could also be final fields
         }
       }
 
-      if (!compact)
+      List<String> possibleValues = parameter.getPossibleValues();
+      isChoiceParameter = possibleValues != null
+              && !possibleValues.isEmpty();
+
+      if (compact)
       {
-        makeExpanderParam(parm);
+        addCompactParameter(parm);
       }
       else
       {
-        makeCompactParam(parm);
-
+        addExpandableParam(parm);
       }
     }
 
-    private void makeCompactParam(ParameterI parm)
+    /**
+     * Adds a 'compact' format parameter, with any help text shown as a tooltip
+     * 
+     * @param parm
+     */
+    private void addCompactParameter(ParameterI parm)
     {
       setLayout(new MigLayout("", "[][grow]"));
-
       String ttipText = null;
 
-      controlPanel.setLayout(new BorderLayout());
+      controlsPanel.setLayout(new BorderLayout());
 
       if (parm.getDescription() != null
               && parm.getDescription().trim().length() > 0)
       {
-        // Only create description boxes if there actually is a description.
-        ttipText = ("<html>"
-                + JvSwingUtils
-                        .wrapTooltip(parm.getDescription()
-                                + (finfo != null ? "<br><img src=\""
-                                        + linkImageURL
-                                        + "\"/> Right click for further information."
-                                        : "")) + "</html>");
+        ttipText = (JvSwingUtils.wrapTooltip(true,
+                parm.getDescription() + (finfo != null ? "<br><img src=\""
+                        + linkImageURL + "\"/>"
+                        + MessageManager.getString(
+                                "label.opt_and_params_further_details")
+                        : "")));
       }
 
-      JvSwingUtils.mgAddtoLayout(this, ttipText,
-              new JLabel(parm.getName()), controlPanel, "");
+      JvSwingUtils.addtoLayout(this, ttipText, new JLabel(parm.getName()),
+              controlsPanel, "");
       updateControls(parm);
       validate();
     }
 
-    private void makeExpanderParam(ParameterI parm)
+    /**
+     * Adds an 'expanded' format parameter, with any help shown in a panel that
+     * may be shown or hidden
+     * 
+     * @param parm
+     */
+    private void addExpandableParam(ParameterI parm)
     {
       setPreferredSize(new Dimension(PARAM_WIDTH, PARAM_CLOSEDHEIGHT));
       setBorder(new TitledBorder(parm.getName()));
       setLayout(null);
-      showDesc.setFont(new Font("Verdana", Font.PLAIN, 6));
-      showDesc.setText("+");
-      string.setFont(new Font("Verdana", Font.PLAIN, 11));
-      string.setBackground(getBackground());
+      descriptionText.setFont(new Font("Verdana", Font.PLAIN, 11));
+      descriptionText.setBackground(getBackground());
 
-      string.setEditable(false);
-      descPanel.getViewport().setView(string);
+      descriptionText.setEditable(false);
+      descPanel.getViewport().setView(descriptionText);
 
       descPanel.setVisible(false);
 
       JPanel firstrow = new JPanel();
       firstrow.setLayout(null);
-      controlPanel.setLayout(new BorderLayout());
-      controlPanel.setBounds(new Rectangle(39, 10, PARAM_WIDTH - 70,
+      controlsPanel.setLayout(new BorderLayout());
+      controlsPanel.setBounds(new Rectangle(39, 10, PARAM_WIDTH - 70,
               PARAM_CLOSEDHEIGHT - 50));
-      firstrow.add(controlPanel);
+      firstrow.add(controlsPanel);
       firstrow.setBounds(new Rectangle(10, 20, PARAM_WIDTH - 30,
               PARAM_CLOSEDHEIGHT - 30));
 
-      final ParamBox me = this;
-
       if (parm.getDescription() != null
               && parm.getDescription().trim().length() > 0)
       {
-        // Only create description boxes if there actually is a description.
-        if (finfo != null)
-        {
-          showDesc.setToolTipText("<html>"
-                  + JvSwingUtils
-                          .wrapTooltip("Click to show brief description<br><img src=\""
-                                  + linkImageURL
-                                  + "\"/> Right click for further information.")
-                  + "</html>");
-          showDesc.addMouseListener(this);
-        }
-        else
-        {
-          showDesc.setToolTipText("<html>"
-                  + JvSwingUtils
-                          .wrapTooltip("Click to show brief description.")
-                  + "</html>");
-        }
-        showDesc.addActionListener(new ActionListener()
-        {
-
-          public void actionPerformed(ActionEvent e)
-          {
-            descisvisible = !descisvisible;
-            descPanel.setVisible(descisvisible);
-            descPanel.getVerticalScrollBar().setValue(0);
-            me.setPreferredSize(new Dimension(PARAM_WIDTH,
-                    (descisvisible) ? PARAM_HEIGHT : PARAM_CLOSEDHEIGHT));
-            me.validate();
-            pmdialogbox.refreshParamLayout();
-          }
-        });
-        string.setWrapStyleWord(true);
-        string.setLineWrap(true);
-        string.setColumns(32);
-        string.setText(parm.getDescription());
-        showDesc.setBounds(new Rectangle(10, 10, 16, 16));
-        firstrow.add(showDesc);
+        addExpandableHelp(firstrow, parm);
       }
       add(firstrow);
       validator = parm.getValidValue();
       parameter = parm;
       if (validator != null)
       {
-        integ = validator.getType() == ValueType.Integer;
+        isIntegerParameter = validator.getType() == ValueType.Integer;
       }
       else
       {
         if (parameter.getPossibleValues() != null)
         {
-          choice = true;
+          isChoiceParameter = true;
         }
       }
       updateControls(parm);
@@ -443,39 +508,94 @@ public class OptsAndParamsPage
       validate();
     }
 
+    /**
+     * Adds a button which can be clicked to show or hide help text
+     * 
+     * @param container
+     * @param param
+     */
+    protected void addExpandableHelp(JPanel container, ParameterI param)
+    {
+      JButton showDescBtn = new JButton("+");
+      showDescBtn.setFont(new Font("Verdana", Font.PLAIN, 8));
+      if (finfo != null)
+      {
+        descTooltip = JvSwingUtils.wrapTooltip(true,
+                MessageManager.formatMessage(
+                        "label.opt_and_params_show_brief_desc_image_link",
+                        new String[]
+                        { linkImageURL.toExternalForm() }));
+        showDescBtn.addMouseListener(this);
+      }
+      else
+      {
+        descTooltip = JvSwingUtils.wrapTooltip(true, MessageManager
+                .getString("label.opt_and_params_show_brief_desc"));
+      }
+      showDescBtn.setToolTipText(descTooltip);
+      showDescBtn.addActionListener(new ActionListener()
+      {
+        @Override
+        public void actionPerformed(ActionEvent e)
+        {
+          descriptionIsVisible = !descriptionIsVisible;
+          showDescBtn.setText(descriptionIsVisible ? "-" : "+");
+          showDescBtn.setToolTipText(
+                  descriptionIsVisible ? null : descTooltip);
+          descPanel.setVisible(descriptionIsVisible);
+          descPanel.getVerticalScrollBar().setValue(0);
+          ParamBox.this.setPreferredSize(new Dimension(PARAM_WIDTH,
+                  (descriptionIsVisible) ? PARAM_HEIGHT
+                          : PARAM_CLOSEDHEIGHT));
+          ParamBox.this.validate();
+          pmdialogbox.refreshParamLayout();
+        }
+      });
+      descriptionText.setWrapStyleWord(true);
+      descriptionText.setLineWrap(true);
+      descriptionText.setColumns(32);
+      descriptionText.setText(param.getDescription());
+      showDescBtn.setBounds(new Rectangle(10, 10, 16, 16));
+      container.add(showDescBtn);
+    }
+
+    @Override
     public void actionPerformed(ActionEvent e)
     {
       if (adjusting)
       {
         return;
       }
-      if (!choice)
+      if (!isChoiceParameter)
       {
         updateSliderFromValueField();
       }
       checkIfModified();
     }
 
+    /**
+     * Checks whether the value of this parameter has been changed and notifies
+     * the parent page accordingly
+     */
     private void checkIfModified()
     {
-      Object cstate = updateSliderFromValueField();
-      boolean notmod = false;
-      if (cstate.getClass() == lastVal.getClass())
+      if (!adjusting)
       {
-        if (cstate instanceof int[])
-        {
-          notmod = (((int[]) cstate)[0] == ((int[]) lastVal)[0]);
-        }
-        else if (cstate instanceof float[])
+        try
         {
-          notmod = (((float[]) cstate)[0] == ((float[]) lastVal)[0]);
-        }
-        else if (cstate instanceof String[])
+          adjusting = true;
+          Object newValue = updateSliderFromValueField();
+          boolean modified = true;
+          if (newValue.getClass() == lastVal.getClass())
+          {
+            modified = !newValue.equals(lastVal);
+          }
+          pmdialogbox.argSetModified(this, modified);
+        } finally
         {
-          notmod = (((String[]) cstate)[0].equals(((String[]) lastVal)[0]));
+          adjusting = false;
         }
       }
-      pmdialogbox.argSetModified(this, !notmod);
     }
 
     @Override
@@ -493,15 +613,10 @@ public class OptsAndParamsPage
       return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
     }
 
-    public int getBoxHeight()
-    {
-      return (descisvisible ? PARAM_HEIGHT : PARAM_CLOSEDHEIGHT);
-    }
-
     public ParameterI getParameter()
     {
       ParameterI prm = parameter.copy();
-      if (choice)
+      if (isChoiceParameter)
       {
         prm.setValue((String) choicebox.getSelectedItem());
       }
@@ -518,110 +633,149 @@ public class OptsAndParamsPage
       lastVal = null;
     }
 
+    @Override
     public void mouseClicked(MouseEvent e)
     {
-      if (javax.swing.SwingUtilities.isRightMouseButton(e))
+      if (e.isPopupTrigger()) // for Windows
       {
         showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
       }
     }
 
+    @Override
     public void mouseEntered(MouseEvent e)
     {
-      // TODO Auto-generated method stub
-
     }
 
+    @Override
     public void mouseExited(MouseEvent e)
     {
-      // TODO Auto-generated method stub
-
     }
 
+    @Override
     public void mousePressed(MouseEvent e)
     {
-      // TODO Auto-generated method stub
-
+      if (e.isPopupTrigger()) // for Mac
+      {
+        showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
+      }
     }
 
+    @Override
     public void mouseReleased(MouseEvent e)
     {
-      // TODO Auto-generated method stub
-
     }
 
+    @Override
     public void stateChanged(ChangeEvent e)
     {
-      if (!adjusting)
+      if (adjusting)
       {
-        valueField.setText(""
-                + ((integ) ? ("" + (int) slider.getValue())
-                        : ("" + (float) (slider.getValue() / 1000f))));
+        return;
+      }
+      try
+      {
+        adjusting = true;
+        if (!isLogarithmicParameter)
+        {
+          /*
+           * set (int or float formatted) text field value
+           */
+          valueField.setText(isIntegerParameter
+                  ? String.valueOf(slider.getValue())
+                  : formatDouble(
+                          slider.getValue() / (float) sliderScaleFactor));
+        }
+        else
+        {
+          double value = Math.pow(Math.E,
+                  slider.getValue() / (double) sliderScaleFactor);
+          valueField.setText(formatDouble(value));
+        }
         checkIfModified();
+      } finally
+      {
+        adjusting = false;
       }
+    }
+
+    /**
+     * Answers the value formatted as a string to 3 decimal places - in
+     * scientific notation if the value is less than 0.001
+     * 
+     * @param value
+     * @return
+     */
+    String formatDouble(double value)
+    {
+      String format = value < 0.001 ? "%3.1E" : "%3.3f";
+      return String.format(format, value);
+    }
 
+    /**
+     * Formats a number as integer or float (3dp) or scientific notation (1dp)
+     * 
+     * @param n
+     * @return
+     */
+    String formatNumber(Number n)
+    {
+      return n instanceof Integer ? String.valueOf(n.intValue())
+              : formatDouble(n.doubleValue());
     }
 
-    public void updateControls(ParameterI parm)
+    void updateControls(ParameterI parm)
     {
       adjusting = true;
       boolean init = (choicebox == null && valueField == null);
       if (init)
       {
-        if (choice)
+        if (isChoiceParameter)
         {
-          choicebox = new JComboBox();
+          choicebox = new JComboBox<>();
           choicebox.addActionListener(this);
-          controlPanel.add(choicebox, BorderLayout.CENTER);
+          controlsPanel.add(choicebox, BorderLayout.CENTER);
         }
         else
         {
           slider = new JSlider();
           slider.addChangeListener(this);
-          valueField = new JTextField();
+          int cols = parm instanceof StringParameter ? 20 : 0;
+          valueField = new JTextField(cols);
           valueField.addActionListener(this);
-          valueField.addKeyListener(new KeyListener()
+          valueField.addKeyListener(new KeyAdapter()
           {
-
-            @Override
-            public void keyTyped(KeyEvent e)
-            {
-            }
-
             @Override
             public void keyReleased(KeyEvent e)
             {
-              if (valueField.getText().trim().length() > 0)
+              int keyCode = e.getKeyCode();
+              if (e.isActionKey() && keyCode != KeyEvent.VK_LEFT
+                      && keyCode != KeyEvent.VK_RIGHT)
               {
-                actionPerformed(null);
+                if (valueField.getText().trim().length() > 0)
+                {
+                  actionPerformed(null);
+                }
               }
             }
-
-            @Override
-            public void keyPressed(KeyEvent e)
-            {
-            }
           });
-          valueField.setPreferredSize(new Dimension(60, 25));
-          controlPanel.add(slider, BorderLayout.WEST);
-          controlPanel.add(valueField, BorderLayout.EAST);
-
+          valueField.setPreferredSize(new Dimension(65, 25));
+          controlsPanel.add(slider, BorderLayout.WEST);
+          controlsPanel.add(valueField, BorderLayout.EAST);
         }
       }
 
       if (parm != null)
       {
-        if (choice)
+        if (isChoiceParameter)
         {
           if (init)
           {
-            List vals = parm.getPossibleValues();
-            for (Object val : vals)
+            for (String val : parm.getPossibleValues())
             {
               choicebox.addItem(val);
             }
           }
-
           if (parm.getValue() != null)
           {
             choicebox.setSelectedItem(parm.getValue());
@@ -636,124 +790,146 @@ public class OptsAndParamsPage
       adjusting = false;
     }
 
-    public Object updateSliderFromValueField()
+    /**
+     * Action depends on the type of the input parameter:
+     * <ul>
+     * <li>if a text input, returns the trimmed value</li>
+     * <li>if a choice list, returns the selected value</li>
+     * <li>if a value slider and input field, sets the value of the slider from
+     * the value in the text field, limiting it to any defined min-max
+     * range.</li>
+     * </ul>
+     * Answers the (possibly modified) input value, as a String, Integer, Float
+     * or Double.
+     * 
+     * @return
+     */
+    Object updateSliderFromValueField()
     {
-      int iVal;
-      float fVal;
-      if (validator != null)
+      if (validator == null || isStringParameter)
       {
-        if (integ)
+        if (isChoiceParameter)
         {
-          iVal = 0;
-          try
-          {
-            valueField.setText(valueField.getText().trim());
-            iVal = Integer.valueOf(valueField.getText());
-            if (validator.getMin() != null
-                    && validator.getMin().intValue() > iVal)
-            {
-              iVal = validator.getMin().intValue();
-              // TODO: provide visual indication that hard limit was reached for
-              // this parameter
-            }
-            if (validator.getMax() != null
-                    && validator.getMax().intValue() < iVal)
-            {
-              iVal = validator.getMax().intValue();
-              // TODO: provide visual indication that hard limit was reached for
-              // this parameter
-            }
-          } catch (Exception e)
-          {
-          }
-          ;
-          // update value field to reflect any bound checking we performed.
-          valueField.setText("" + iVal);
-          if (validator.getMin() != null && validator.getMax() != null)
-          {
-            slider.getModel().setRangeProperties(iVal, 1,
-                    validator.getMin().intValue(),
-                    validator.getMax().intValue(), true);
-          }
-          else
-          {
-            slider.setVisible(false);
-          }
-          return new int[]
-          { iVal };
+          return choicebox.getSelectedItem();
         }
-        else
+        slider.setVisible(false);
+        return valueField.getText().trim();
+      }
+
+      valueField.setText(valueField.getText().trim());
+
+      /*
+       * ensure not outside min-max range
+       * TODO: provide some visual indicator if limit reached
+       */
+      try
+      {
+        valueField.setBackground(Color.WHITE);
+        double d = Double.parseDouble(valueField.getText());
+        if (validator.getMin() != null
+                && validator.getMin().doubleValue() > d)
         {
-          fVal = 0f;
-          try
-          {
-            valueField.setText(valueField.getText().trim());
-            fVal = Float.valueOf(valueField.getText());
-            if (validator.getMin() != null
-                    && validator.getMin().floatValue() > fVal)
-            {
-              fVal = validator.getMin().floatValue();
-              // TODO: provide visual indication that hard limit was reached for
-              // this parameter
-              // update value field to reflect any bound checking we performed.
-              valueField.setText("" + fVal);
-            }
-            if (validator.getMax() != null
-                    && validator.getMax().floatValue() < fVal)
-            {
-              fVal = validator.getMax().floatValue();
-              // TODO: provide visual indication that hard limit was reached for
-              // this parameter
-              // update value field to reflect any bound checking we performed.
-              valueField.setText("" + fVal);
-            }
-          } catch (Exception e)
-          {
-          }
-          ;
-          if (validator.getMin() != null && validator.getMax() != null)
-          {
-            slider.getModel().setRangeProperties((int) fVal * 1000, 1,
-                    (int) validator.getMin().floatValue() * 1000,
-                    (int) validator.getMax().floatValue() * 1000, true);
-          }
-          else
-          {
-            slider.setVisible(false);
-          }
-          return new float[]
-          { fVal };
+          valueField.setText(formatNumber(validator.getMin()));
         }
+        if (validator.getMax() != null
+                && validator.getMax().doubleValue() < d)
+        {
+          valueField.setText(formatNumber(validator.getMax()));
+        }
+      } catch (NumberFormatException e)
+      {
+        valueField.setBackground(Color.yellow);
+        return Float.NaN;
       }
-      else
+
+      if (isIntegerParameter)
       {
-        if (!choice)
+        int iVal = 0;
+        try
+        {
+          iVal = Integer.valueOf(valueField.getText());
+        } catch (Exception e)
+        {
+          valueField.setBackground(Color.yellow);
+          return Integer.valueOf(0);
+        }
+
+        if (validator.getMin() != null && validator.getMax() != null)
+        {
+          slider.getModel().setRangeProperties(iVal, 1,
+                  validator.getMin().intValue(),
+                  validator.getMax().intValue() + 1, true);
+        }
+        else
         {
           slider.setVisible(false);
-          return new String[]
-          { valueField.getText().trim() };
+        }
+        return new Integer(iVal);
+      }
+
+      if (isLogarithmicParameter)
+      {
+        double dVal = 0d;
+        try
+        {
+          double eValue = Double.valueOf(valueField.getText());
+          dVal = Math.log(eValue) * sliderScaleFactor;
+        } catch (Exception e)
+        {
+          // shouldn't be possible here
+          valueField.setBackground(Color.yellow);
+          return Double.NaN;
+        }
+        if (validator.getMin() != null && validator.getMax() != null)
+        {
+          double scaleMin = Math.log(validator.getMin().doubleValue())
+                  * sliderScaleFactor;
+          double scaleMax = Math.log(validator.getMax().doubleValue())
+                  * sliderScaleFactor;
+          slider.getModel().setRangeProperties((int) (dVal), 1,
+                  (int) scaleMin, 1 + (int) scaleMax, true);
         }
         else
         {
-          return new String[]
-          { (String) choicebox.getSelectedItem() };
+          slider.setVisible(false);
         }
+        return new Double(dVal);
       }
 
+      float fVal = 0f;
+      try
+      {
+        fVal = Float.valueOf(valueField.getText());
+      } catch (Exception e)
+      {
+        return Float.valueOf(0f); // shouldn't happen
+      }
+      if (validator.getMin() != null && validator.getMax() != null)
+      {
+        float scaleMin = validator.getMin().floatValue()
+                * sliderScaleFactor;
+        float scaleMax = validator.getMax().floatValue()
+                * sliderScaleFactor;
+        slider.getModel().setRangeProperties(
+                (int) (fVal * sliderScaleFactor), 1, (int) scaleMin,
+                1 + (int) scaleMax, true);
+      }
+      else
+      {
+        slider.setVisible(false);
+      }
+      return new Float(fVal);
     }
   }
 
-  public static final int PARAM_WIDTH = 340;
-
-  public static final int PARAM_HEIGHT = 150;
-
-  public static final int PARAM_CLOSEDHEIGHT = 80;
-
-  public OptsAndParamsPage(OptsParametersContainerI paramContainer)
-  {
-    this(paramContainer, false);
-  }
-
+  /**
+   * Constructor with the option to show 'compact' format (parameter description
+   * as tooltip) or 'expanded' format (parameter description in a textbox which
+   * may be opened or closed). Use compact for simple description text, expanded
+   * for more wordy or formatted text.
+   * 
+   * @param paramContainer
+   */
   public OptsAndParamsPage(OptsParametersContainerI paramContainer,
           boolean compact)
   {
@@ -766,7 +942,9 @@ public class OptsAndParamsPage
   {
 
     JPopupMenu mnu = new JPopupMenu();
-    JMenuItem mitem = new JMenuItem("View " + finfo);
+    JMenuItem mitem = new JMenuItem(
+            MessageManager.formatMessage("label.view_params", new String[]
+            { finfo }));
     mitem.addActionListener(new ActionListener()
     {
 
@@ -781,12 +959,6 @@ public class OptsAndParamsPage
     mnu.show(invoker, x, y);
   }
 
-  URL linkImageURL = getClass().getResource("/images/link.gif");
-
-  Map<String, OptionBox> optSet = new Hashtable<String, OptionBox>();
-
-  Map<String, ParamBox> paramSet = new Hashtable<String, ParamBox>();
-
   public Map<String, OptionBox> getOptSet()
   {
     return optSet;
@@ -807,8 +979,6 @@ public class OptsAndParamsPage
     this.paramSet = paramSet;
   }
 
-  OptsParametersContainerI poparent;
-
   OptionBox addOption(OptionI opt)
   {
     OptionBox cb = optSet.get(opt.getName());
@@ -852,9 +1022,9 @@ public class OptsAndParamsPage
       }
       else
       {
-        throw new Error("Invalid value " + string + " for option " + option);
+        throw new Error(String.format("Invalid value '%s' for option '%s'",
+                string, option.getName()));
       }
-
     }
     if (option.isRequired() && !cb.enabled.isSelected())
     {
@@ -884,7 +1054,7 @@ public class OptsAndParamsPage
    */
   public List<ArgumentI> getCurrentSettings()
   {
-    List<ArgumentI> argSet = new ArrayList<ArgumentI>();
+    List<ArgumentI> argSet = new ArrayList<>();
     for (OptionBox opts : getOptSet().values())
     {
       OptionI opt = opts.getOptionIfEnabled();
@@ -904,5 +1074,4 @@ public class OptsAndParamsPage
 
     return argSet;
   }
-
 }