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.params.ArgumentI;
25 import jalview.ws.params.OptionI;
26 import jalview.ws.params.ParameterI;
27 import jalview.ws.params.ValueConstrainI;
28 import jalview.ws.params.ValueConstrainI.ValueType;
29 import jalview.ws.params.simple.LogarithmicParameter;
30 import jalview.ws.params.simple.StringParameter;
32 import java.awt.BorderLayout;
33 import java.awt.Color;
34 import java.awt.Component;
35 import java.awt.Dimension;
36 import java.awt.FlowLayout;
38 import java.awt.Rectangle;
39 import java.awt.event.ActionEvent;
40 import java.awt.event.ActionListener;
41 import java.awt.event.KeyAdapter;
42 import java.awt.event.KeyEvent;
43 import java.awt.event.MouseEvent;
44 import java.awt.event.MouseListener;
46 import java.util.ArrayList;
47 import java.util.LinkedHashMap;
48 import java.util.List;
51 import javax.swing.JButton;
52 import javax.swing.JCheckBox;
53 import javax.swing.JComboBox;
54 import javax.swing.JComponent;
55 import javax.swing.JLabel;
56 import javax.swing.JMenuItem;
57 import javax.swing.JPanel;
58 import javax.swing.JPopupMenu;
59 import javax.swing.JScrollPane;
60 import javax.swing.JSlider;
61 import javax.swing.JTextArea;
62 import javax.swing.JTextField;
63 import javax.swing.border.TitledBorder;
64 import javax.swing.event.ChangeEvent;
65 import javax.swing.event.ChangeListener;
67 import net.miginfocom.swing.MigLayout;
70 * GUI generator/manager for options and parameters. Originally abstracted from
71 * the WsJobParameters dialog box.
76 public class OptsAndParamsPage
78 public static final int PARAM_WIDTH = 340;
80 public static final int PARAM_HEIGHT = 150;
82 public static final int PARAM_CLOSEDHEIGHT = 80;
84 URL linkImageURL = getClass().getResource("/images/link.gif");
86 Map<String, OptionBox> optSet = new LinkedHashMap<>();
88 Map<String, ParamBox> paramSet = new LinkedHashMap<>();
91 * compact or verbose style parameters
93 boolean compact = false;
95 OptsParametersContainerI poparent;
98 * A class that models a panel rendering a single option (checkbox or choice
101 public class OptionBox extends JPanel
102 implements MouseListener, ActionListener
108 boolean hasLink = false;
110 boolean initEnabled = false;
112 String initVal = null;
116 JComboBox<String> val;
118 public OptionBox(OptionI opt)
121 setLayout(new FlowLayout(FlowLayout.LEFT));
122 enabled = new JCheckBox();
123 enabled.setSelected(opt.isRequired()); // TODO: lock required options
124 enabled.setFont(new Font("Verdana", Font.PLAIN, 11));
126 enabled.setText(opt.getName());
127 enabled.addActionListener(this);
128 finfo = option.getFurtherDetails();
129 String desc = opt.getDescription();
134 enabled.setToolTipText(JvSwingUtils.wrapTooltip(true,
135 ((desc == null || desc.trim().length() == 0)
136 ? MessageManager.getString(
137 "label.opt_and_params_further_details")
138 : desc) + "<br><img src=\"" + linkImageURL
140 enabled.addMouseListener(this);
144 if (desc != null && desc.trim().length() > 0)
146 enabled.setToolTipText(
147 JvSwingUtils.wrapTooltip(true, opt.getDescription()));
152 // todo combo or radio buttons?
153 val = new JComboBox<>();
154 for (String str : opt.getPossibleValues())
158 val.setSelectedItem(opt.getValue());
159 if (opt.getPossibleValues().size() > 1 || opt.isRequired())
161 val.addActionListener(this);
164 // TODO: add actionListeners for popup (to open further info),
165 // and to update list of parameters if an option is enabled
166 // that takes a value. JBPNote: is this TODO still valid ?
171 public void actionPerformed(ActionEvent e)
173 if (e.getSource() != enabled)
175 enabled.setSelected(true);
180 private void checkIfModified()
182 boolean notmod = (initEnabled == enabled.isSelected());
183 if (enabled.isSelected())
187 notmod &= initVal.equals(val.getSelectedItem());
191 // compare against default service setting
192 notmod &= option.getValue() == null
193 || option.getValue().equals(val.getSelectedItem());
198 notmod &= (initVal != null) ? initVal.equals(val.getSelectedItem())
199 : val.getSelectedItem() != initVal;
201 poparent.argSetModified(this, !notmod);
204 public OptionI getOptionIfEnabled()
206 if (!enabled.isSelected())
210 OptionI opt = option.copy();
211 if (opt.getPossibleValues() != null
212 && opt.getPossibleValues().size() == 1)
214 // Hack to make sure the default value for an enabled option with only
215 // one value is actually returned
216 opt.setValue(opt.getPossibleValues().get(0));
218 if (val.getSelectedItem() != null)
220 opt.setValue((String) val.getSelectedItem());
224 if (option.getValue() != null)
226 opt.setValue(option.getValue());
233 public void mouseClicked(MouseEvent e)
235 if (e.isPopupTrigger()) // for Windows
237 showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
242 public void mouseEntered(MouseEvent e)
247 public void mouseExited(MouseEvent e)
252 public void mousePressed(MouseEvent e)
254 if (e.isPopupTrigger()) // Mac
256 showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
261 public void mouseReleased(MouseEvent e)
265 public void resetToDefault(boolean setDefaultParams)
267 enabled.setSelected(false);
268 if (option.isRequired()
269 || (setDefaultParams && option.getValue() != null))
271 // Apply default value
272 selectOption(option, option.getValue());
276 public void setInitialValue()
278 initEnabled = enabled.isSelected();
279 if (option.getPossibleValues() != null
280 && option.getPossibleValues().size() > 1)
282 initVal = (String) val.getSelectedItem();
286 initVal = (initEnabled) ? (String) val.getSelectedItem() : null;
291 * toString representation for identification in the debugger only
294 public String toString()
296 return option == null ? super.toString() : option.toString();
302 * A class that models a panel rendering a single parameter
304 public class ParamBox extends JPanel
305 implements ChangeListener, ActionListener, MouseListener
308 * parameter values (or their logs) are multiplied by this
309 * scaling factor to ensure an integer range for the slider
311 private int sliderScaleFactor = 1;
313 boolean isLogarithmicParameter;
315 boolean isChoiceParameter;
317 boolean isIntegerParameter;
319 boolean isStringParameter;
323 JComboBox<String> choicebox;
325 JPanel controlsPanel = new JPanel();
327 boolean descriptionIsVisible = false;
329 JScrollPane descPanel = new JScrollPane();
335 ParameterI parameter;
337 final OptsParametersContainerI pmdialogbox;
339 JPanel settingPanel = new JPanel();
343 JTextArea descriptionText = new JTextArea();
345 ValueConstrainI validator;
347 JTextField valueField;
349 private String descTooltip;
351 public ParamBox(final OptsParametersContainerI paramContainer,
354 pmdialogbox = paramContainer;
355 finfo = parm.getFurtherDetails();
356 validator = parm.getValidValue();
359 isLogarithmicParameter = parm instanceof LogarithmicParameter;
361 if (validator != null)
363 ValueType type = validator.getType();
364 isIntegerParameter = type == ValueType.Integer;
365 isStringParameter = type == ValueType.String;
368 * ensure slider has an integer range corresponding to
369 * the min-max range of the parameter
371 if (validator.getMin() != null && validator.getMax() != null
372 // && !isIntegerParameter
373 && !isStringParameter)
375 double min = validator.getMin().doubleValue();
376 double max = validator.getMax().doubleValue();
377 if (isLogarithmicParameter)
382 sliderScaleFactor = (int) (1000000 / (max - min));
383 // todo scaleMin, scaleMax could also be final fields
387 List<String> possibleValues = parameter.getPossibleValues();
388 isChoiceParameter = possibleValues != null
389 && !possibleValues.isEmpty();
393 addCompactParameter(parm);
397 addExpandableParam(parm);
402 * Adds a 'compact' format parameter, with any help text shown as a tooltip
406 private void addCompactParameter(ParameterI parm)
408 setLayout(new MigLayout("", "[][grow]"));
409 String ttipText = null;
411 controlsPanel.setLayout(new BorderLayout());
413 if (parm.getDescription() != null
414 && parm.getDescription().trim().length() > 0)
416 ttipText = (JvSwingUtils.wrapTooltip(true,
417 parm.getDescription() + (finfo != null ? "<br><img src=\""
418 + linkImageURL + "\"/>"
419 + MessageManager.getString(
420 "label.opt_and_params_further_details")
424 JvSwingUtils.addtoLayout(this, ttipText, new JLabel(parm.getName()),
426 updateControls(parm);
431 * Adds an 'expanded' format parameter, with any help shown in a panel that
432 * may be shown or hidden
436 private void addExpandableParam(ParameterI parm)
438 setPreferredSize(new Dimension(PARAM_WIDTH, PARAM_CLOSEDHEIGHT));
439 setBorder(new TitledBorder(parm.getName()));
441 descriptionText.setFont(new Font("Verdana", Font.PLAIN, 11));
442 descriptionText.setBackground(getBackground());
444 descriptionText.setEditable(false);
445 descPanel.getViewport().setView(descriptionText);
447 descPanel.setVisible(false);
449 JPanel firstrow = new JPanel();
450 firstrow.setLayout(null);
451 controlsPanel.setLayout(new BorderLayout());
452 controlsPanel.setBounds(new Rectangle(39, 10, PARAM_WIDTH - 70,
453 PARAM_CLOSEDHEIGHT - 50));
454 firstrow.add(controlsPanel);
455 firstrow.setBounds(new Rectangle(10, 20, PARAM_WIDTH - 30,
456 PARAM_CLOSEDHEIGHT - 30));
458 if (parm.getDescription() != null
459 && parm.getDescription().trim().length() > 0)
461 addExpandableHelp(firstrow, parm);
464 validator = parm.getValidValue();
466 if (validator != null)
468 isIntegerParameter = validator.getType() == ValueType.Integer;
472 if (parameter.getPossibleValues() != null)
474 isChoiceParameter = true;
477 updateControls(parm);
478 descPanel.setBounds(new Rectangle(10, PARAM_CLOSEDHEIGHT,
479 PARAM_WIDTH - 20, PARAM_HEIGHT - PARAM_CLOSEDHEIGHT - 5));
485 * Adds a button which can be clicked to show or hide help text
490 protected void addExpandableHelp(JPanel container, ParameterI param)
492 JButton showDescBtn = new JButton("+");
493 showDescBtn.setFont(new Font("Verdana", Font.PLAIN, 8));
496 descTooltip = JvSwingUtils.wrapTooltip(true,
497 MessageManager.formatMessage(
498 "label.opt_and_params_show_brief_desc_image_link",
500 { linkImageURL.toExternalForm() }));
501 showDescBtn.addMouseListener(this);
505 descTooltip = JvSwingUtils.wrapTooltip(true, MessageManager
506 .getString("label.opt_and_params_show_brief_desc"));
508 showDescBtn.setToolTipText(descTooltip);
509 showDescBtn.addActionListener(new ActionListener()
512 public void actionPerformed(ActionEvent e)
514 descriptionIsVisible = !descriptionIsVisible;
515 showDescBtn.setText(descriptionIsVisible ? "-" : "+");
516 showDescBtn.setToolTipText(
517 descriptionIsVisible ? null : descTooltip);
518 descPanel.setVisible(descriptionIsVisible);
519 descPanel.getVerticalScrollBar().setValue(0);
520 ParamBox.this.setPreferredSize(new Dimension(PARAM_WIDTH,
521 (descriptionIsVisible) ? PARAM_HEIGHT
522 : PARAM_CLOSEDHEIGHT));
523 ParamBox.this.validate();
524 pmdialogbox.refreshParamLayout();
527 descriptionText.setWrapStyleWord(true);
528 descriptionText.setLineWrap(true);
529 descriptionText.setColumns(32);
530 descriptionText.setText(param.getDescription());
531 showDescBtn.setBounds(new Rectangle(10, 10, 16, 16));
532 container.add(showDescBtn);
536 public void actionPerformed(ActionEvent e)
542 if (!isChoiceParameter)
544 updateSliderFromValueField();
550 * Checks whether the value of this parameter has been changed and notifies
551 * the parent page accordingly
553 private void checkIfModified()
560 Object newValue = updateSliderFromValueField();
561 boolean modified = true;
562 if (newValue.getClass() == lastVal.getClass())
564 modified = !newValue.equals(lastVal);
566 pmdialogbox.argSetModified(this, modified);
575 public int getBaseline(int width, int height)
581 // http://stackoverflow.com/questions/2743177/top-alignment-for-flowlayout
582 // helpful hint of using the Java 1.6 alignBaseLine property of FlowLayout
584 public Component.BaselineResizeBehavior getBaselineResizeBehavior()
586 return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
589 public ParameterI getParameter()
591 ParameterI prm = parameter.copy();
592 if (isChoiceParameter)
594 prm.setValue((String) choicebox.getSelectedItem());
598 prm.setValue(valueField.getText());
605 // reset the widget's initial value.
610 public void mouseClicked(MouseEvent e)
612 if (e.isPopupTrigger()) // for Windows
614 showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
619 public void mouseEntered(MouseEvent e)
624 public void mouseExited(MouseEvent e)
629 public void mousePressed(MouseEvent e)
631 if (e.isPopupTrigger()) // for Mac
633 showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
638 public void mouseReleased(MouseEvent e)
643 public void stateChanged(ChangeEvent e)
652 if (!isLogarithmicParameter)
655 * set (int or float formatted) text field value
657 valueField.setText(isIntegerParameter
658 ? String.valueOf(slider.getValue())
660 slider.getValue() / (float) sliderScaleFactor));
664 double value = Math.pow(Math.E,
665 slider.getValue() / (double) sliderScaleFactor);
666 valueField.setText(formatDouble(value));
676 * Answers the value formatted as a string to 3 decimal places - in
677 * scientific notation if the value is less than 0.001
682 String formatDouble(double value)
684 String format = value < 0.001 ? "%3.1E" : "%3.3f";
685 return String.format(format, value);
689 * Formats a number as integer or float (3dp) or scientific notation (1dp)
694 String formatNumber(Number n)
696 return n instanceof Integer ? String.valueOf(n.intValue())
697 : formatDouble(n.doubleValue());
700 void updateControls(ParameterI parm)
703 boolean init = (choicebox == null && valueField == null);
706 if (isChoiceParameter)
708 choicebox = new JComboBox<>();
709 choicebox.addActionListener(this);
710 controlsPanel.add(choicebox, BorderLayout.CENTER);
714 slider = new JSlider();
715 slider.addChangeListener(this);
716 int cols = parm instanceof StringParameter ? 20 : 0;
717 valueField = new JTextField(cols);
718 valueField.addActionListener(this);
719 valueField.addKeyListener(new KeyAdapter()
722 public void keyReleased(KeyEvent e)
724 int keyCode = e.getKeyCode();
725 if (e.isActionKey() && keyCode != KeyEvent.VK_LEFT
726 && keyCode != KeyEvent.VK_RIGHT)
728 if (valueField.getText().trim().length() > 0)
730 actionPerformed(null);
735 valueField.setPreferredSize(new Dimension(65, 25));
736 controlsPanel.add(slider, BorderLayout.WEST);
737 controlsPanel.add(valueField, BorderLayout.EAST);
743 if (isChoiceParameter)
747 for (String val : parm.getPossibleValues())
749 choicebox.addItem(val);
752 if (parm.getValue() != null)
754 choicebox.setSelectedItem(parm.getValue());
759 valueField.setText(parm.getValue());
762 lastVal = updateSliderFromValueField();
767 * Action depends on the type of the input parameter:
769 * <li>if a text input, returns the trimmed value</li>
770 * <li>if a choice list, returns the selected value</li>
771 * <li>if a value slider and input field, sets the value of the slider from
772 * the value in the text field, limiting it to any defined min-max
775 * Answers the (possibly modified) input value, as a String, Integer, Float
780 Object updateSliderFromValueField()
782 if (validator == null || isStringParameter)
784 if (isChoiceParameter)
786 return choicebox.getSelectedItem();
788 slider.setVisible(false);
789 return valueField.getText().trim();
792 valueField.setText(valueField.getText().trim());
795 * ensure not outside min-max range
796 * TODO: provide some visual indicator if limit reached
800 valueField.setBackground(Color.WHITE);
801 double d = Double.parseDouble(valueField.getText());
802 if (validator.getMin() != null
803 && validator.getMin().doubleValue() > d)
805 valueField.setText(formatNumber(validator.getMin()));
807 if (validator.getMax() != null
808 && validator.getMax().doubleValue() < d)
810 valueField.setText(formatNumber(validator.getMax()));
812 } catch (NumberFormatException e)
814 valueField.setBackground(Color.yellow);
818 if (isIntegerParameter)
823 iVal = Integer.valueOf(valueField.getText());
824 } catch (Exception e)
826 valueField.setBackground(Color.yellow);
827 return Integer.valueOf(0);
830 if (validator.getMin() != null && validator.getMax() != null)
832 slider.getModel().setRangeProperties(iVal, 1,
833 validator.getMin().intValue(),
834 validator.getMax().intValue() + 1, true);
838 slider.setVisible(false);
840 return new Integer(iVal);
843 if (isLogarithmicParameter)
848 double eValue = Double.valueOf(valueField.getText());
849 dVal = Math.log(eValue) * sliderScaleFactor;
850 } catch (Exception e)
852 // shouldn't be possible here
853 valueField.setBackground(Color.yellow);
856 if (validator.getMin() != null && validator.getMax() != null)
858 double scaleMin = Math.log(validator.getMin().doubleValue())
860 double scaleMax = Math.log(validator.getMax().doubleValue())
862 slider.getModel().setRangeProperties((int) (dVal), 1,
863 (int) scaleMin, 1 + (int) scaleMax, true);
867 slider.setVisible(false);
869 return new Double(dVal);
875 fVal = Float.valueOf(valueField.getText());
876 } catch (Exception e)
878 return Float.valueOf(0f); // shouldn't happen
880 if (validator.getMin() != null && validator.getMax() != null)
882 float scaleMin = validator.getMin().floatValue()
884 float scaleMax = validator.getMax().floatValue()
886 slider.getModel().setRangeProperties(
887 (int) (fVal * sliderScaleFactor), 1, (int) scaleMin,
888 1 + (int) scaleMax, true);
892 slider.setVisible(false);
894 return new Float(fVal);
899 * Constructor with the option to show 'compact' format (parameter description
900 * as tooltip) or 'expanded' format (parameter description in a textbox which
901 * may be opened or closed). Use compact for simple description text, expanded
902 * for more wordy or formatted text.
904 * @param paramContainer
906 public OptsAndParamsPage(OptsParametersContainerI paramContainer,
909 poparent = paramContainer;
910 this.compact = compact;
913 public static void showUrlPopUp(JComponent invoker, final String finfo,
917 JPopupMenu mnu = new JPopupMenu();
918 JMenuItem mitem = new JMenuItem(
919 MessageManager.formatMessage("label.view_params", new String[]
921 mitem.addActionListener(new ActionListener()
925 public void actionPerformed(ActionEvent e)
927 Desktop.showUrl(finfo);
932 mnu.show(invoker, x, y);
935 public Map<String, OptionBox> getOptSet()
940 public void setOptSet(Map<String, OptionBox> optSet)
942 this.optSet = optSet;
945 public Map<String, ParamBox> getParamSet()
950 public void setParamSet(Map<String, ParamBox> paramSet)
952 this.paramSet = paramSet;
955 OptionBox addOption(OptionI opt)
957 OptionBox cb = optSet.get(opt.getName());
960 cb = new OptionBox(opt);
961 optSet.put(opt.getName(), cb);
962 // jobOptions.add(cb, FlowLayout.LEFT);
967 ParamBox addParameter(ParameterI arg)
969 ParamBox pb = paramSet.get(arg.getName());
972 pb = new ParamBox(poparent, arg);
973 paramSet.put(arg.getName(), pb);
974 // paramList.add(pb);
977 // take the defaults from the parameter
978 pb.updateControls(arg);
982 void selectOption(OptionI option, String string)
984 OptionBox cb = optSet.get(option.getName());
987 cb = addOption(option);
989 cb.enabled.setSelected(string != null); // initial state for an option.
992 if (option.getPossibleValues().contains(string))
994 cb.val.setSelectedItem(string);
998 throw new Error(String.format("Invalid value '%s' for option '%s'",
999 string, option.getName()));
1002 if (option.isRequired() && !cb.enabled.isSelected())
1004 // TODO: indicate paramset is not valid.. option needs to be selected!
1006 cb.setInitialValue();
1009 void setParameter(ParameterI arg)
1011 ParamBox pb = paramSet.get(arg.getName());
1018 pb.updateControls(arg);
1024 * recover options and parameters from GUI
1028 public List<ArgumentI> getCurrentSettings()
1030 List<ArgumentI> argSet = new ArrayList<>();
1031 for (OptionBox opts : getOptSet().values())
1033 OptionI opt = opts.getOptionIfEnabled();
1039 for (ParamBox parambox : getParamSet().values())
1041 ParameterI parm = parambox.getParameter();