2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
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;
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;
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;
47 import java.util.ArrayList;
48 import java.util.LinkedHashMap;
49 import java.util.List;
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;
68 import net.miginfocom.swing.MigLayout;
71 * GUI generator/manager for options and parameters. Originally abstracted from
72 * the WsJobParameters dialog box.
77 public class OptsAndParamsPage
79 public static final int PARAM_WIDTH = 340;
81 public static final int PARAM_HEIGHT = 150;
83 public static final int PARAM_CLOSEDHEIGHT = 80;
85 URL linkImageURL = getClass().getResource("/images/link.gif");
87 Map<String, OptionBox> optSet = new LinkedHashMap<>();
89 Map<String, ParamBox> paramSet = new LinkedHashMap<>();
92 * compact or verbose style parameters
94 boolean compact = false;
96 OptsParametersContainerI poparent;
99 * A class that models a panel rendering a single option (checkbox or choice
102 public class OptionBox extends JPanel
103 implements MouseListener, ActionListener
109 boolean hasLink = false;
111 boolean initEnabled = false;
113 String initVal = null;
117 JComboBox<String> val;
120 * Constructs and adds labels and controls to the panel for one Option
124 public OptionBox(OptionI opt)
127 setLayout(new FlowLayout(FlowLayout.LEFT));
128 enabled = new JCheckBox(opt.getName());
129 enabled.setSelected(opt.isRequired());
132 * If option is required, show a label, if optional a checkbox
133 * (but not for Jabaws pending JWS-126 resolution)
135 if (opt.isRequired() && !(opt instanceof JabaOption))
138 add(new JLabel(opt.getName()));
142 finfo = option.getFurtherDetails();
143 configureCheckbox(opt);
148 * construct the choice box with possible values,
149 * or their display names if provided
151 List<String> displayNames = opt.getDisplayNames();
152 if (displayNames != null)
154 val = JvSwingUtils.buildComboWithTooltips(displayNames,
155 opt.getPossibleValues());
159 val = new JComboBox<>();
160 for (String v : opt.getPossibleValues())
165 val.setSelectedItem(opt.getValue());
168 * only show the choicebox if there is more than one option,
169 * or the option is mandatory
171 if (opt.getPossibleValues().size() > 1 || opt.isRequired())
173 val.addActionListener(this);
181 * Configures the checkbox that controls whether or not the option is
186 protected void configureCheckbox(OptionI opt)
188 enabled.setFont(new Font("Verdana", Font.PLAIN, 11));
189 enabled.addActionListener(this);
190 final String desc = opt.getDescription();
194 String description = desc;
195 if (desc == null || desc.trim().isEmpty())
197 description = MessageManager
198 .getString("label.opt_and_params_further_details");
200 description = description + "<br><img src=\"" + linkImageURL
202 String text = JvSwingUtils.wrapTooltip(true, description);
203 enabled.setToolTipText(text);
204 enabled.addMouseListener(this); // for popup menu to show link
208 if (desc != null && desc.trim().length() > 0)
210 enabled.setToolTipText(JvSwingUtils.wrapTooltip(true, desc));
216 public void actionPerformed(ActionEvent e)
218 if (e.getSource() != enabled)
220 enabled.setSelected(true);
225 private void checkIfModified()
227 boolean notmod = (initEnabled == enabled.isSelected());
228 if (enabled.isSelected())
232 notmod &= initVal.equals(val.getSelectedItem());
236 // compare against default service setting
237 notmod &= option.getValue() == null
238 || option.getValue().equals(val.getSelectedItem());
243 notmod &= (initVal != null) ? initVal.equals(val.getSelectedItem())
244 : val.getSelectedItem() != initVal;
246 poparent.argSetModified(this, !notmod);
250 * Answers null if the option is not selected, else a new Option holding the
255 public OptionI getSelectedOption()
257 if (!enabled.isSelected())
261 String value = getSelectedValue();
262 OptionI opt = option.copy();
268 * Answers the value corresponding to the selected item in the choice combo
269 * box. If display names were not provided, this is simply the selected
270 * value. If display names were provided, it is the value corresponding to
271 * the selected item index.
275 protected String getSelectedValue()
277 List<String> possibleValues = option.getPossibleValues();
279 if (possibleValues != null && possibleValues.size() == 1)
281 // Hack to make sure the default value for an enabled option with only
282 // one value is actually returned even if this.val is not displayed
283 value = possibleValues.get(0);
287 int sel = val.getSelectedIndex();
288 if (sel >= 0 && sel < possibleValues.size())
290 value = possibleValues.get(sel);
294 value = option.getValue();
301 public void mouseClicked(MouseEvent e)
303 if (e.isPopupTrigger()) // for Windows
305 showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
310 public void mouseEntered(MouseEvent e)
315 public void mouseExited(MouseEvent e)
320 public void mousePressed(MouseEvent e)
322 if (e.isPopupTrigger()) // Mac
324 showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
329 public void mouseReleased(MouseEvent e)
333 public void resetToDefault(boolean setDefaultParams)
335 enabled.setSelected(false);
336 if (option.isRequired()
337 || (setDefaultParams && option.getValue() != null))
339 // Apply default value
340 selectOption(option, option.getValue());
344 public void setInitialValue()
346 initEnabled = enabled.isSelected();
347 if (option.getPossibleValues() != null
348 && option.getPossibleValues().size() > 1)
350 initVal = (String) val.getSelectedItem();
354 initVal = (initEnabled) ? (String) val.getSelectedItem() : null;
359 * toString representation for identification in the debugger only
362 public String toString()
364 return option == null ? super.toString() : option.toString();
370 * A class that models a panel rendering a single parameter
372 public class ParamBox extends JPanel
373 implements ChangeListener, ActionListener, MouseListener
376 * parameter values (or their logs) are multiplied by this
377 * scaling factor to ensure an integer range for the slider
379 private int sliderScaleFactor = 1;
381 boolean isLogarithmicParameter;
383 boolean isChoiceParameter;
385 boolean isIntegerParameter;
387 boolean isStringParameter;
391 JComboBox<String> choicebox;
393 JPanel controlsPanel = new JPanel();
395 boolean descriptionIsVisible = false;
397 JScrollPane descPanel = new JScrollPane();
403 ParameterI parameter;
405 final OptsParametersContainerI pmdialogbox;
407 JPanel settingPanel = new JPanel();
411 JTextArea descriptionText = new JTextArea();
413 ValueConstrainI validator;
415 JTextField valueField;
417 private String descTooltip;
419 public ParamBox(final OptsParametersContainerI paramContainer,
422 pmdialogbox = paramContainer;
423 finfo = parm.getFurtherDetails();
424 validator = parm.getValidValue();
427 isLogarithmicParameter = parm instanceof LogarithmicParameter;
429 if (validator != null)
431 ValueType type = validator.getType();
432 isIntegerParameter = type == ValueType.Integer;
433 isStringParameter = type == ValueType.String;
436 * ensure slider has an integer range corresponding to
437 * the min-max range of the parameter
439 if (validator.getMin() != null && validator.getMax() != null
440 // && !isIntegerParameter
441 && !isStringParameter)
443 double min = validator.getMin().doubleValue();
444 double max = validator.getMax().doubleValue();
445 if (isLogarithmicParameter)
450 sliderScaleFactor = (int) (1000000 / (max - min));
451 // todo scaleMin, scaleMax could also be final fields
455 List<String> possibleValues = parameter.getPossibleValues();
456 isChoiceParameter = possibleValues != null
457 && !possibleValues.isEmpty();
461 addCompactParameter(parm);
465 addExpandableParam(parm);
470 * Adds a 'compact' format parameter, with any help text shown as a tooltip
474 private void addCompactParameter(ParameterI parm)
476 setLayout(new MigLayout("", "[][grow]"));
477 String ttipText = null;
479 controlsPanel.setLayout(new BorderLayout());
481 if (parm.getDescription() != null
482 && parm.getDescription().trim().length() > 0)
484 ttipText = (JvSwingUtils.wrapTooltip(true,
485 parm.getDescription() + (finfo != null ? "<br><img src=\""
486 + linkImageURL + "\"/>"
487 + MessageManager.getString(
488 "label.opt_and_params_further_details")
492 JvSwingUtils.addtoLayout(this, ttipText, new JLabel(parm.getName()),
494 updateControls(parm);
499 * Adds an 'expanded' format parameter, with any help shown in a panel that
500 * may be shown or hidden
504 private void addExpandableParam(ParameterI parm)
506 setPreferredSize(new Dimension(PARAM_WIDTH, PARAM_CLOSEDHEIGHT));
507 setBorder(new TitledBorder(parm.getName()));
509 descriptionText.setFont(new Font("Verdana", Font.PLAIN, 11));
510 descriptionText.setBackground(getBackground());
512 descriptionText.setEditable(false);
513 descPanel.getViewport().setView(descriptionText);
515 descPanel.setVisible(false);
517 JPanel firstrow = new JPanel();
518 firstrow.setLayout(null);
519 controlsPanel.setLayout(new BorderLayout());
520 controlsPanel.setBounds(new Rectangle(39, 10, PARAM_WIDTH - 70,
521 PARAM_CLOSEDHEIGHT - 50));
522 firstrow.add(controlsPanel);
523 firstrow.setBounds(new Rectangle(10, 20, PARAM_WIDTH - 30,
524 PARAM_CLOSEDHEIGHT - 30));
526 if (parm.getDescription() != null
527 && parm.getDescription().trim().length() > 0)
529 addExpandableHelp(firstrow, parm);
532 validator = parm.getValidValue();
534 if (validator != null)
536 isIntegerParameter = validator.getType() == ValueType.Integer;
540 if (parameter.getPossibleValues() != null)
542 isChoiceParameter = true;
545 updateControls(parm);
546 descPanel.setBounds(new Rectangle(10, PARAM_CLOSEDHEIGHT,
547 PARAM_WIDTH - 20, PARAM_HEIGHT - PARAM_CLOSEDHEIGHT - 5));
553 * Adds a button which can be clicked to show or hide help text
558 protected void addExpandableHelp(JPanel container, ParameterI param)
560 JButton showDescBtn = new JButton("+");
561 showDescBtn.setFont(new Font("Verdana", Font.PLAIN, 8));
564 descTooltip = JvSwingUtils.wrapTooltip(true,
565 MessageManager.formatMessage(
566 "label.opt_and_params_show_brief_desc_image_link",
568 { linkImageURL.toExternalForm() }));
569 showDescBtn.addMouseListener(this);
573 descTooltip = JvSwingUtils.wrapTooltip(true, MessageManager
574 .getString("label.opt_and_params_show_brief_desc"));
576 showDescBtn.setToolTipText(descTooltip);
577 showDescBtn.addActionListener(new ActionListener()
580 public void actionPerformed(ActionEvent e)
582 descriptionIsVisible = !descriptionIsVisible;
583 showDescBtn.setText(descriptionIsVisible ? "-" : "+");
584 showDescBtn.setToolTipText(
585 descriptionIsVisible ? null : descTooltip);
586 descPanel.setVisible(descriptionIsVisible);
587 descPanel.getVerticalScrollBar().setValue(0);
588 ParamBox.this.setPreferredSize(new Dimension(PARAM_WIDTH,
589 (descriptionIsVisible) ? PARAM_HEIGHT
590 : PARAM_CLOSEDHEIGHT));
591 ParamBox.this.validate();
592 pmdialogbox.refreshParamLayout();
595 descriptionText.setWrapStyleWord(true);
596 descriptionText.setLineWrap(true);
597 descriptionText.setColumns(32);
598 descriptionText.setText(param.getDescription());
599 showDescBtn.setBounds(new Rectangle(10, 10, 16, 16));
600 container.add(showDescBtn);
604 public void actionPerformed(ActionEvent e)
610 if (!isChoiceParameter)
612 updateSliderFromValueField();
618 * Checks whether the value of this parameter has been changed and notifies
619 * the parent page accordingly
621 private void checkIfModified()
628 Object newValue = updateSliderFromValueField();
629 boolean modified = true;
630 if (newValue.getClass() == lastVal.getClass())
632 modified = !newValue.equals(lastVal);
634 pmdialogbox.argSetModified(this, modified);
643 public int getBaseline(int width, int height)
649 // http://stackoverflow.com/questions/2743177/top-alignment-for-flowlayout
650 // helpful hint of using the Java 1.6 alignBaseLine property of FlowLayout
652 public Component.BaselineResizeBehavior getBaselineResizeBehavior()
654 return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
657 public ParameterI getParameter()
659 ParameterI prm = parameter.copy();
660 if (isChoiceParameter)
662 String value = getChoice();
667 prm.setValue(valueField.getText());
673 * Answers the value corresponding to the selected item in the choice combo
674 * box. If display names were not provided, this is simply the selected
675 * value. If display names were provided, it is the value corresponding to
676 * the selected item index.
680 protected String getChoice()
682 int sel = choicebox.getSelectedIndex();
683 if (sel < 0 || sel >= parameter.getPossibleValues().size())
687 return parameter.getPossibleValues().get(sel);
692 // reset the widget's initial value.
697 public void mouseClicked(MouseEvent e)
699 if (e.isPopupTrigger()) // for Windows
701 showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
706 public void mouseEntered(MouseEvent e)
711 public void mouseExited(MouseEvent e)
716 public void mousePressed(MouseEvent e)
718 if (e.isPopupTrigger()) // for Mac
720 showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
725 public void mouseReleased(MouseEvent e)
730 public void stateChanged(ChangeEvent e)
739 if (!isLogarithmicParameter)
742 * set (int or float formatted) text field value
744 valueField.setText(isIntegerParameter
745 ? String.valueOf(slider.getValue())
747 slider.getValue() / (float) sliderScaleFactor));
751 double value = Math.pow(Math.E,
752 slider.getValue() / (double) sliderScaleFactor);
753 valueField.setText(formatDouble(value));
763 * Answers the value formatted as a string to 3 decimal places - in
764 * scientific notation if the value is less than 0.001
769 String formatDouble(double value)
771 String format = value < 0.001 ? "%3.1E" : "%3.3f";
772 return String.format(format, value);
776 * Formats a number as integer or float (3dp) or scientific notation (1dp)
781 String formatNumber(Number n)
783 return n instanceof Integer ? String.valueOf(n.intValue())
784 : formatDouble(n.doubleValue());
787 void updateControls(ParameterI parm)
790 boolean init = (choicebox == null && valueField == null);
791 List<String> displayNames = parm.getDisplayNames();
794 if (isChoiceParameter)
796 if (displayNames != null)
798 choicebox = JvSwingUtils.buildComboWithTooltips(displayNames,
799 parm.getPossibleValues());
803 choicebox = new JComboBox<>();
804 for (String val : parm.getPossibleValues())
806 choicebox.addItem(val);
809 choicebox.addActionListener(this);
810 controlsPanel.add(choicebox, BorderLayout.CENTER);
814 slider = new JSlider();
815 slider.addChangeListener(this);
816 int cols = parm instanceof StringParameter ? 20 : 0;
817 valueField = new JTextField(cols);
818 valueField.addActionListener(this);
819 valueField.addKeyListener(new KeyAdapter()
822 public void keyReleased(KeyEvent e)
824 int keyCode = e.getKeyCode();
825 if (e.isActionKey() && keyCode != KeyEvent.VK_LEFT
826 && keyCode != KeyEvent.VK_RIGHT)
828 if (valueField.getText().trim().length() > 0)
830 actionPerformed(null);
835 valueField.setPreferredSize(new Dimension(65, 25));
836 controlsPanel.add(slider, BorderLayout.WEST);
837 controlsPanel.add(valueField, BorderLayout.EAST);
841 if (!isChoiceParameter && parm != null)
843 valueField.setText(parm.getValue());
845 lastVal = updateSliderFromValueField();
850 * Action depends on the type of the input parameter:
852 * <li>if a text input, returns the trimmed value</li>
853 * <li>if a choice list, returns the selected value</li>
854 * <li>if a value slider and input field, sets the value of the slider from
855 * the value in the text field, limiting it to any defined min-max
858 * Answers the (possibly modified) input value, as a String, Integer, Float
863 Object updateSliderFromValueField()
865 if (validator == null || isStringParameter)
867 if (isChoiceParameter)
871 slider.setVisible(false);
872 return valueField.getText().trim();
875 valueField.setText(valueField.getText().trim());
878 * ensure not outside min-max range
879 * TODO: provide some visual indicator if limit reached
883 valueField.setBackground(Color.WHITE);
884 double d = Double.parseDouble(valueField.getText());
885 if (validator.getMin() != null
886 && validator.getMin().doubleValue() > d)
888 valueField.setText(formatNumber(validator.getMin()));
890 if (validator.getMax() != null
891 && validator.getMax().doubleValue() < d)
893 valueField.setText(formatNumber(validator.getMax()));
895 } catch (NumberFormatException e)
897 valueField.setBackground(Color.yellow);
901 if (isIntegerParameter)
906 iVal = Integer.valueOf(valueField.getText());
907 } catch (Exception e)
909 valueField.setBackground(Color.yellow);
910 return Integer.valueOf(0);
913 if (validator.getMin() != null && validator.getMax() != null)
915 slider.getModel().setRangeProperties(iVal, 1,
916 validator.getMin().intValue(),
917 validator.getMax().intValue() + 1, true);
921 slider.setVisible(false);
923 return new Integer(iVal);
926 if (isLogarithmicParameter)
931 double eValue = Double.valueOf(valueField.getText());
932 dVal = Math.log(eValue) * sliderScaleFactor;
933 } catch (Exception e)
935 // shouldn't be possible here
936 valueField.setBackground(Color.yellow);
939 if (validator.getMin() != null && validator.getMax() != null)
941 double scaleMin = Math.log(validator.getMin().doubleValue())
943 double scaleMax = Math.log(validator.getMax().doubleValue())
945 slider.getModel().setRangeProperties((int) (dVal), 1,
946 (int) scaleMin, 1 + (int) scaleMax, true);
950 slider.setVisible(false);
952 return new Double(dVal);
958 fVal = Float.valueOf(valueField.getText());
959 } catch (Exception e)
961 return Float.valueOf(0f); // shouldn't happen
963 if (validator.getMin() != null && validator.getMax() != null)
965 float scaleMin = validator.getMin().floatValue()
967 float scaleMax = validator.getMax().floatValue()
969 slider.getModel().setRangeProperties(
970 (int) (fVal * sliderScaleFactor), 1, (int) scaleMin,
971 1 + (int) scaleMax, true);
975 slider.setVisible(false);
977 return new Float(fVal);
982 * Constructor with the option to show 'compact' format (parameter description
983 * as tooltip) or 'expanded' format (parameter description in a textbox which
984 * may be opened or closed). Use compact for simple description text, expanded
985 * for more wordy or formatted text.
987 * @param paramContainer
989 public OptsAndParamsPage(OptsParametersContainerI paramContainer,
992 poparent = paramContainer;
993 this.compact = compact;
996 public static void showUrlPopUp(JComponent invoker, final String finfo,
1000 JPopupMenu mnu = new JPopupMenu();
1001 JMenuItem mitem = new JMenuItem(
1002 MessageManager.formatMessage("label.view_params", new String[]
1004 mitem.addActionListener(new ActionListener()
1008 public void actionPerformed(ActionEvent e)
1010 Desktop.showUrl(finfo);
1015 mnu.show(invoker, x, y);
1018 public Map<String, OptionBox> getOptSet()
1023 public void setOptSet(Map<String, OptionBox> optSet)
1025 this.optSet = optSet;
1028 public Map<String, ParamBox> getParamSet()
1033 public void setParamSet(Map<String, ParamBox> paramSet)
1035 this.paramSet = paramSet;
1038 OptionBox addOption(OptionI opt)
1040 OptionBox cb = optSet.get(opt.getName());
1043 cb = new OptionBox(opt);
1044 optSet.put(opt.getName(), cb);
1045 // jobOptions.add(cb, FlowLayout.LEFT);
1050 ParamBox addParameter(ParameterI arg)
1052 ParamBox pb = paramSet.get(arg.getName());
1055 pb = new ParamBox(poparent, arg);
1056 paramSet.put(arg.getName(), pb);
1057 // paramList.add(pb);
1060 // take the defaults from the parameter
1061 pb.updateControls(arg);
1065 void selectOption(OptionI option, String string)
1067 OptionBox cb = optSet.get(option.getName());
1070 cb = addOption(option);
1072 cb.enabled.setSelected(string != null); // initial state for an option.
1075 if (option.getPossibleValues().contains(string))
1077 cb.val.setSelectedItem(string);
1081 throw new Error(String.format("Invalid value '%s' for option '%s'",
1082 string, option.getName()));
1085 if (option.isRequired() && !cb.enabled.isSelected())
1087 // TODO: indicate paramset is not valid.. option needs to be selected!
1089 cb.setInitialValue();
1092 void setParameter(ParameterI arg)
1094 ParamBox pb = paramSet.get(arg.getName());
1101 pb.updateControls(arg);
1107 * recover options and parameters from GUI
1111 public List<ArgumentI> getCurrentSettings()
1113 List<ArgumentI> argSet = new ArrayList<>();
1114 for (OptionBox opts : getOptSet().values())
1116 OptionI opt = opts.getSelectedOption();
1122 for (ParamBox parambox : getParamSet().values())
1124 ParameterI parm = parambox.getParameter();