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 static jalview.ws.params.simple.LogarithmicParameter.LOGSLIDERSCALE;
25 import jalview.bin.Cache;
26 import jalview.util.MessageManager;
27 import jalview.ws.params.ArgumentI;
28 import jalview.ws.params.OptionI;
29 import jalview.ws.params.ParameterI;
30 import jalview.ws.params.ValueConstrainI;
31 import jalview.ws.params.ValueConstrainI.ValueType;
32 import jalview.ws.params.simple.LogarithmicParameter;
33 import jalview.ws.params.simple.StringParameter;
35 import java.awt.BorderLayout;
36 import java.awt.Component;
37 import java.awt.Dimension;
38 import java.awt.FlowLayout;
40 import java.awt.Rectangle;
41 import java.awt.event.ActionEvent;
42 import java.awt.event.ActionListener;
43 import java.awt.event.KeyAdapter;
44 import java.awt.event.KeyEvent;
45 import java.awt.event.MouseEvent;
46 import java.awt.event.MouseListener;
48 import java.util.ArrayList;
49 import java.util.LinkedHashMap;
50 import java.util.List;
53 import javax.swing.JButton;
54 import javax.swing.JCheckBox;
55 import javax.swing.JComboBox;
56 import javax.swing.JComponent;
57 import javax.swing.JLabel;
58 import javax.swing.JMenuItem;
59 import javax.swing.JPanel;
60 import javax.swing.JPopupMenu;
61 import javax.swing.JScrollPane;
62 import javax.swing.JSlider;
63 import javax.swing.JTextArea;
64 import javax.swing.JTextField;
65 import javax.swing.border.TitledBorder;
66 import javax.swing.event.ChangeEvent;
67 import javax.swing.event.ChangeListener;
69 import net.miginfocom.swing.MigLayout;
72 * GUI generator/manager for options and parameters. Originally abstracted from
73 * the WsJobParameters dialog box.
78 public class OptsAndParamsPage
80 public static final int PARAM_WIDTH = 340;
82 public static final int PARAM_HEIGHT = 150;
84 public static final int PARAM_CLOSEDHEIGHT = 80;
86 URL linkImageURL = getClass().getResource("/images/link.gif");
88 Map<String, OptionBox> optSet = new LinkedHashMap<>();
90 Map<String, ParamBox> paramSet = new LinkedHashMap<>();
93 * compact or verbose style parameters
95 boolean compact = false;
97 OptsParametersContainerI poparent;
100 * A class that models a panel rendering a single option (checkbox or choice
103 public class OptionBox extends JPanel
104 implements MouseListener, ActionListener
106 JCheckBox enabled = new JCheckBox();
110 boolean hasLink = false;
112 boolean initEnabled = false;
114 String initVal = null;
118 JLabel optlabel = new JLabel();
120 JComboBox<String> val = new JComboBox<>();
122 public OptionBox(OptionI opt)
125 setLayout(new FlowLayout(FlowLayout.LEFT));
126 enabled.setSelected(opt.isRequired()); // TODO: lock required options
127 enabled.setFont(new Font("Verdana", Font.PLAIN, 11));
129 enabled.setText(opt.getName());
130 enabled.addActionListener(this);
131 finfo = option.getFurtherDetails();
132 String desc = opt.getDescription();
137 enabled.setToolTipText(JvSwingUtils.wrapTooltip(true,
138 ((desc == null || desc.trim().length() == 0)
139 ? MessageManager.getString(
140 "label.opt_and_params_further_details")
141 : desc) + "<br><img src=\"" + linkImageURL
143 enabled.addMouseListener(this);
147 if (desc != null && desc.trim().length() > 0)
149 enabled.setToolTipText(
150 JvSwingUtils.wrapTooltip(true, opt.getDescription()));
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
307 private static final float SLIDERSCALE = 1000f;
309 boolean isLogarithmicParameter;
311 boolean isChoiceParameter;
313 boolean isIntegerParameter;
317 JComboBox<String> choicebox;
319 JPanel controlsPanel = new JPanel();
321 boolean descriptionIsVisible = false;
323 JScrollPane descPanel = new JScrollPane();
329 ParameterI parameter;
331 final OptsParametersContainerI pmdialogbox;
333 JPanel settingPanel = new JPanel();
337 JTextArea descriptionText = new JTextArea();
339 ValueConstrainI validator;
341 JTextField valueField;
343 private String descTooltip;
345 public ParamBox(final OptsParametersContainerI paramContainer,
348 pmdialogbox = paramContainer;
349 finfo = parm.getFurtherDetails();
350 validator = parm.getValidValue();
352 if (validator != null)
354 isIntegerParameter = validator.getType() == ValueType.Integer;
356 else if (parameter.getPossibleValues() != null)
358 isChoiceParameter = true;
360 if (parm instanceof LogarithmicParameter)
362 isLogarithmicParameter = true;
367 addCompactParameter(parm);
371 addExpandableParam(parm);
376 * Adds a 'compact' format parameter, with any help text shown as a tooltip
380 private void addCompactParameter(ParameterI parm)
382 setLayout(new MigLayout("", "[][grow]"));
383 String ttipText = null;
385 controlsPanel.setLayout(new BorderLayout());
387 if (parm.getDescription() != null
388 && parm.getDescription().trim().length() > 0)
390 ttipText = (JvSwingUtils.wrapTooltip(true,
391 parm.getDescription() + (finfo != null ? "<br><img src=\""
392 + linkImageURL + "\"/>"
393 + MessageManager.getString(
394 "label.opt_and_params_further_details")
398 JvSwingUtils.addtoLayout(this, ttipText, new JLabel(parm.getName()),
400 updateControls(parm);
405 * Adds an 'expanded' format parameter, with any help shown in a panel that
406 * may be shown or hidden
410 private void addExpandableParam(ParameterI parm)
412 setPreferredSize(new Dimension(PARAM_WIDTH, PARAM_CLOSEDHEIGHT));
413 setBorder(new TitledBorder(parm.getName()));
415 descriptionText.setFont(new Font("Verdana", Font.PLAIN, 11));
416 descriptionText.setBackground(getBackground());
418 descriptionText.setEditable(false);
419 descPanel.getViewport().setView(descriptionText);
421 descPanel.setVisible(false);
423 JPanel firstrow = new JPanel();
424 firstrow.setLayout(null);
425 controlsPanel.setLayout(new BorderLayout());
426 controlsPanel.setBounds(new Rectangle(39, 10, PARAM_WIDTH - 70,
427 PARAM_CLOSEDHEIGHT - 50));
428 firstrow.add(controlsPanel);
429 firstrow.setBounds(new Rectangle(10, 20, PARAM_WIDTH - 30,
430 PARAM_CLOSEDHEIGHT - 30));
432 if (parm.getDescription() != null
433 && parm.getDescription().trim().length() > 0)
435 addExpandableHelp(firstrow, parm);
438 validator = parm.getValidValue();
440 if (validator != null)
442 isIntegerParameter = validator.getType() == ValueType.Integer;
446 if (parameter.getPossibleValues() != null)
448 isChoiceParameter = true;
451 updateControls(parm);
452 descPanel.setBounds(new Rectangle(10, PARAM_CLOSEDHEIGHT,
453 PARAM_WIDTH - 20, PARAM_HEIGHT - PARAM_CLOSEDHEIGHT - 5));
459 * Adds a button which can be clicked to show or hide help text
464 protected void addExpandableHelp(JPanel container, ParameterI param)
466 JButton showDescBtn = new JButton("+");
467 showDescBtn.setFont(new Font("Verdana", Font.PLAIN, 8));
470 descTooltip = JvSwingUtils.wrapTooltip(true,
471 MessageManager.formatMessage(
472 "label.opt_and_params_show_brief_desc_image_link",
474 { linkImageURL.toExternalForm() }));
475 showDescBtn.addMouseListener(this);
479 descTooltip = JvSwingUtils.wrapTooltip(true, MessageManager
480 .getString("label.opt_and_params_show_brief_desc"));
482 showDescBtn.setToolTipText(descTooltip);
483 showDescBtn.addActionListener(new ActionListener()
486 public void actionPerformed(ActionEvent e)
488 descriptionIsVisible = !descriptionIsVisible;
489 showDescBtn.setText(descriptionIsVisible ? "-" : "+");
490 showDescBtn.setToolTipText(
491 descriptionIsVisible ? null : descTooltip);
492 descPanel.setVisible(descriptionIsVisible);
493 descPanel.getVerticalScrollBar().setValue(0);
494 ParamBox.this.setPreferredSize(new Dimension(PARAM_WIDTH,
495 (descriptionIsVisible) ? PARAM_HEIGHT
496 : PARAM_CLOSEDHEIGHT));
497 ParamBox.this.validate();
498 pmdialogbox.refreshParamLayout();
501 descriptionText.setWrapStyleWord(true);
502 descriptionText.setLineWrap(true);
503 descriptionText.setColumns(32);
504 descriptionText.setText(param.getDescription());
505 showDescBtn.setBounds(new Rectangle(10, 10, 16, 16));
506 container.add(showDescBtn);
510 public void actionPerformed(ActionEvent e)
516 if (!isChoiceParameter)
518 updateSliderFromValueField();
524 * Checks whether the value of this parameter has been changed and notifies
525 * the parent page accordingly
527 private void checkIfModified()
529 Object newValue = updateSliderFromValueField();
530 boolean modified = true;
531 if (newValue.getClass() == lastVal.getClass())
533 modified = !newValue.equals(lastVal);
535 pmdialogbox.argSetModified(this, modified);
539 public int getBaseline(int width, int height)
545 // http://stackoverflow.com/questions/2743177/top-alignment-for-flowlayout
546 // helpful hint of using the Java 1.6 alignBaseLine property of FlowLayout
548 public Component.BaselineResizeBehavior getBaselineResizeBehavior()
550 return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
553 public ParameterI getParameter()
555 ParameterI prm = parameter.copy();
556 if (isChoiceParameter)
558 prm.setValue((String) choicebox.getSelectedItem());
562 prm.setValue(valueField.getText());
569 // reset the widget's initial value.
574 public void mouseClicked(MouseEvent e)
576 if (e.isPopupTrigger()) // for Windows
578 showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
583 public void mouseEntered(MouseEvent e)
585 // TODO Auto-generated method stub
590 public void mouseExited(MouseEvent e)
592 // TODO Auto-generated method stub
597 public void mousePressed(MouseEvent e)
599 if (e.isPopupTrigger()) // for Mac
601 showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
606 public void mouseReleased(MouseEvent e)
608 // TODO Auto-generated method stub
613 public void stateChanged(ChangeEvent e)
617 if (!isLogarithmicParameter)
620 * set (int or float formatted) text field value
622 valueField.setText(isIntegerParameter ? String.valueOf(slider.getValue())
623 : String.valueOf(slider.getValue() / SLIDERSCALE));
627 double base = ((LogarithmicParameter) parameter).getBase();
628 double value = Math.pow(base, slider.getValue() / LOGSLIDERSCALE);
629 valueField.setText(formatDouble(value));
637 * Answers the value formatted as a string to 3 decimal places - in
638 * scientific notation if the value is less than 0.001
643 String formatDouble(double value)
645 String format = value < 0.001 ? "%3.3e" : "%3.3f";
646 return String.format(format, value);
649 void updateControls(ParameterI parm)
652 boolean init = (choicebox == null && valueField == null);
655 if (isChoiceParameter)
657 choicebox = new JComboBox<>();
658 choicebox.addActionListener(this);
659 controlsPanel.add(choicebox, BorderLayout.CENTER);
663 slider = new JSlider();
664 slider.addChangeListener(this);
665 int cols = parm instanceof StringParameter ? 20 : 0;
666 valueField = new JTextField(cols);
667 valueField.addActionListener(this);
668 valueField.addKeyListener(new KeyAdapter()
671 public void keyReleased(KeyEvent e)
675 if (valueField.getText().trim().length() > 0)
677 actionPerformed(null);
682 valueField.setPreferredSize(new Dimension(60, 25));
683 controlsPanel.add(slider, BorderLayout.WEST);
684 controlsPanel.add(valueField, BorderLayout.EAST);
690 if (isChoiceParameter)
694 for (String val : parm.getPossibleValues())
696 choicebox.addItem(val);
700 if (parm.getValue() != null)
702 choicebox.setSelectedItem(parm.getValue());
707 if (parm instanceof LogarithmicParameter)
709 double base = ((LogarithmicParameter) parm).getBase();
710 // double value = Math.pow(base,
711 // Double.parseDouble(parm.getValue()) / LOGSLIDERSCALE);
712 double value = Double.parseDouble(parm.getValue());
713 valueField.setText(formatDouble(value));
717 valueField.setText(parm.getValue());
721 lastVal = updateSliderFromValueField();
726 * Action depends on the type of the input parameter:
728 * <li>if a text input, returns the trimmed value</li>
729 * <li>if a choice list, returns the selected value</li>
730 * <li>if a value slider and input field, sets the value of the slider from
731 * the value in the text field, limiting it to any defined min-max
734 * Answers the (possibly modified) input value, as a String, Integer, Float
739 Object updateSliderFromValueField()
741 if (validator == null)
743 if (!isChoiceParameter)
745 slider.setVisible(false);
746 return valueField.getText().trim();
750 return choicebox.getSelectedItem();
753 if (isIntegerParameter)
758 valueField.setText(valueField.getText().trim());
759 iVal = Integer.valueOf(valueField.getText());
762 * ensure not outside min-max range
763 * TODO: provide some visual indicator if limit reached
765 if (validator.getMin() != null
766 && validator.getMin().intValue() > iVal)
768 iVal = validator.getMin().intValue();
769 valueField.setText(String.valueOf(iVal));
771 if (validator.getMax() != null
772 && validator.getMax().intValue() < iVal)
774 iVal = validator.getMax().intValue();
775 valueField.setText(String.valueOf(iVal));
777 } catch (Exception e)
779 Cache.log.error(e.getMessage());
782 if (validator.getMin() != null && validator.getMax() != null)
784 slider.getModel().setRangeProperties(iVal, 1,
785 validator.getMin().intValue(),
786 validator.getMax().intValue() + 1, true);
790 slider.setVisible(false);
792 return new Integer(iVal);
794 else if (isLogarithmicParameter)
799 valueField.setText(valueField.getText().trim());
800 double eValue = Double.valueOf(valueField.getText());
802 dVal = Math.log(eValue)
803 / Math.log(((LogarithmicParameter) parameter).getBase())
807 * ensure not outside min-max range
808 * TODO: provide some visual indicator if limit reached
810 if (validator.getMin() != null
811 && validator.getMin().doubleValue() > dVal)
813 dVal = validator.getMin().doubleValue();
814 valueField.setText(formatDouble(eValue));
816 if (validator.getMax() != null
817 && validator.getMax().doubleValue() < dVal)
819 dVal = validator.getMax().doubleValue();
820 valueField.setText(formatDouble(eValue));
822 } catch (Exception e)
826 if (validator.getMin() != null && validator.getMax() != null)
828 slider.getModel().setRangeProperties((int) (dVal), 1,
829 (int) (validator.getMin().doubleValue()),
830 1 + (int) (validator.getMax().doubleValue()), true);
834 slider.setVisible(false);
836 return new Double(dVal);
843 valueField.setText(valueField.getText().trim());
844 fVal = Float.valueOf(valueField.getText());
847 * ensure not outside min-max range
848 * TODO: provide some visual indicator if limit reached
850 if (validator.getMin() != null
851 && validator.getMin().floatValue() > fVal)
853 fVal = validator.getMin().floatValue();
854 valueField.setText(String.valueOf(fVal));
856 if (validator.getMax() != null
857 && validator.getMax().floatValue() < fVal)
859 fVal = validator.getMax().floatValue();
860 valueField.setText(String.valueOf(fVal));
862 } catch (Exception e)
866 if (validator.getMin() != null && validator.getMax() != null)
868 slider.getModel().setRangeProperties((int) (fVal * SLIDERSCALE),
869 1, (int) (validator.getMin().floatValue() * SLIDERSCALE),
870 1 + (int) (validator.getMax().floatValue() * SLIDERSCALE),
875 slider.setVisible(false);
877 return new Float(fVal);
883 * Constructor with the option to show 'compact' format (parameter description
884 * as tooltip) or 'expanded' format (parameter description in a textbox which
885 * may be opened or closed). Use compact for simple description text, expanded
886 * for more wordy or formatted text.
888 * @param paramContainer
890 public OptsAndParamsPage(OptsParametersContainerI paramContainer,
893 poparent = paramContainer;
894 this.compact = compact;
897 public static void showUrlPopUp(JComponent invoker, final String finfo,
901 JPopupMenu mnu = new JPopupMenu();
902 JMenuItem mitem = new JMenuItem(
903 MessageManager.formatMessage("label.view_params", new String[]
905 mitem.addActionListener(new ActionListener()
909 public void actionPerformed(ActionEvent e)
911 Desktop.showUrl(finfo);
916 mnu.show(invoker, x, y);
919 public Map<String, OptionBox> getOptSet()
924 public void setOptSet(Map<String, OptionBox> optSet)
926 this.optSet = optSet;
929 public Map<String, ParamBox> getParamSet()
934 public void setParamSet(Map<String, ParamBox> paramSet)
936 this.paramSet = paramSet;
939 OptionBox addOption(OptionI opt)
941 OptionBox cb = optSet.get(opt.getName());
944 cb = new OptionBox(opt);
945 optSet.put(opt.getName(), cb);
946 // jobOptions.add(cb, FlowLayout.LEFT);
951 ParamBox addParameter(ParameterI arg)
953 ParamBox pb = paramSet.get(arg.getName());
956 pb = new ParamBox(poparent, arg);
957 paramSet.put(arg.getName(), pb);
958 // paramList.add(pb);
961 // take the defaults from the parameter
962 pb.updateControls(arg);
966 void selectOption(OptionI option, String string)
968 OptionBox cb = optSet.get(option.getName());
971 cb = addOption(option);
973 cb.enabled.setSelected(string != null); // initial state for an option.
976 if (option.getPossibleValues().contains(string))
978 cb.val.setSelectedItem(string);
982 throw new Error(String.format("Invalid value '%s' for option '%s'",
983 string, option.getName()));
986 if (option.isRequired() && !cb.enabled.isSelected())
988 // TODO: indicate paramset is not valid.. option needs to be selected!
990 cb.setInitialValue();
993 void setParameter(ParameterI arg)
995 ParamBox pb = paramSet.get(arg.getName());
1002 pb.updateControls(arg);
1008 * recover options and parameters from GUI
1012 public List<ArgumentI> getCurrentSettings()
1014 List<ArgumentI> argSet = new ArrayList<>();
1015 for (OptionBox opts : getOptSet().values())
1017 OptionI opt = opts.getOptionIfEnabled();
1023 for (ParamBox parambox : getParamSet().values())
1025 ParameterI parm = parambox.getParameter();