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
104 JCheckBox enabled = new JCheckBox();
108 boolean hasLink = false;
110 boolean initEnabled = false;
112 String initVal = null;
116 JLabel optlabel = new JLabel();
118 JComboBox<String> val = new JComboBox<>();
120 public OptionBox(OptionI opt)
123 setLayout(new FlowLayout(FlowLayout.LEFT));
124 enabled.setSelected(opt.isRequired()); // TODO: lock required options
125 enabled.setFont(new Font("Verdana", Font.PLAIN, 11));
127 enabled.setText(opt.getName());
128 enabled.addActionListener(this);
129 finfo = option.getFurtherDetails();
130 String desc = opt.getDescription();
135 enabled.setToolTipText(JvSwingUtils.wrapTooltip(true,
136 ((desc == null || desc.trim().length() == 0)
137 ? MessageManager.getString(
138 "label.opt_and_params_further_details")
139 : desc) + "<br><img src=\"" + linkImageURL
141 enabled.addMouseListener(this);
145 if (desc != null && desc.trim().length() > 0)
147 enabled.setToolTipText(
148 JvSwingUtils.wrapTooltip(true, opt.getDescription()));
152 for (String str : opt.getPossibleValues())
156 val.setSelectedItem(opt.getValue());
157 if (opt.getPossibleValues().size() > 1 || opt.isRequired())
159 val.addActionListener(this);
162 // TODO: add actionListeners for popup (to open further info),
163 // and to update list of parameters if an option is enabled
164 // that takes a value. JBPNote: is this TODO still valid ?
169 public void actionPerformed(ActionEvent e)
171 if (e.getSource() != enabled)
173 enabled.setSelected(true);
178 private void checkIfModified()
180 boolean notmod = (initEnabled == enabled.isSelected());
181 if (enabled.isSelected())
185 notmod &= initVal.equals(val.getSelectedItem());
189 // compare against default service setting
190 notmod &= option.getValue() == null
191 || option.getValue().equals(val.getSelectedItem());
196 notmod &= (initVal != null) ? initVal.equals(val.getSelectedItem())
197 : val.getSelectedItem() != initVal;
199 poparent.argSetModified(this, !notmod);
202 public OptionI getOptionIfEnabled()
204 if (!enabled.isSelected())
208 OptionI opt = option.copy();
209 if (opt.getPossibleValues() != null
210 && opt.getPossibleValues().size() == 1)
212 // Hack to make sure the default value for an enabled option with only
213 // one value is actually returned
214 opt.setValue(opt.getPossibleValues().get(0));
216 if (val.getSelectedItem() != null)
218 opt.setValue((String) val.getSelectedItem());
222 if (option.getValue() != null)
224 opt.setValue(option.getValue());
231 public void mouseClicked(MouseEvent e)
233 if (e.isPopupTrigger()) // for Windows
235 showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
240 public void mouseEntered(MouseEvent e)
245 public void mouseExited(MouseEvent e)
250 public void mousePressed(MouseEvent e)
252 if (e.isPopupTrigger()) // Mac
254 showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
259 public void mouseReleased(MouseEvent e)
263 public void resetToDefault(boolean setDefaultParams)
265 enabled.setSelected(false);
266 if (option.isRequired()
267 || (setDefaultParams && option.getValue() != null))
269 // Apply default value
270 selectOption(option, option.getValue());
274 public void setInitialValue()
276 initEnabled = enabled.isSelected();
277 if (option.getPossibleValues() != null
278 && option.getPossibleValues().size() > 1)
280 initVal = (String) val.getSelectedItem();
284 initVal = (initEnabled) ? (String) val.getSelectedItem() : null;
289 * toString representation for identification in the debugger only
292 public String toString()
294 return option == null ? super.toString() : option.toString();
300 * A class that models a panel rendering a single parameter
302 public class ParamBox extends JPanel
303 implements ChangeListener, ActionListener, MouseListener
306 * parameter values (or their logs) are multiplied by this
307 * scaling factor to ensure an integer range for the slider
309 private int sliderScaleFactor = 1;
311 boolean isLogarithmicParameter;
313 boolean isChoiceParameter;
315 boolean isIntegerParameter;
319 JComboBox<String> choicebox;
321 JPanel controlsPanel = new JPanel();
323 boolean descriptionIsVisible = false;
325 JScrollPane descPanel = new JScrollPane();
331 ParameterI parameter;
333 final OptsParametersContainerI pmdialogbox;
335 JPanel settingPanel = new JPanel();
339 JTextArea descriptionText = new JTextArea();
341 ValueConstrainI validator;
343 JTextField valueField;
345 private String descTooltip;
347 public ParamBox(final OptsParametersContainerI paramContainer,
350 pmdialogbox = paramContainer;
351 finfo = parm.getFurtherDetails();
352 validator = parm.getValidValue();
355 isLogarithmicParameter = parm instanceof LogarithmicParameter;
357 if (validator != null)
359 ValueType type = validator.getType();
360 isIntegerParameter = type == ValueType.Integer;
363 * ensure slider has an integer range corresponding to
364 * the min-max range of the parameter
366 if (validator.getMin() != null && !isIntegerParameter)
368 double min = validator.getMin().doubleValue();
369 double max = validator.getMax().doubleValue();
370 if (isLogarithmicParameter)
375 sliderScaleFactor = (int) (1000000 / (max - min));
376 // todo scaleMin, scaleMax could also be final fields
381 isChoiceParameter = parameter.getPossibleValues() != null;
386 addCompactParameter(parm);
390 addExpandableParam(parm);
395 * Adds a 'compact' format parameter, with any help text shown as a tooltip
399 private void addCompactParameter(ParameterI parm)
401 setLayout(new MigLayout("", "[][grow]"));
402 String ttipText = null;
404 controlsPanel.setLayout(new BorderLayout());
406 if (parm.getDescription() != null
407 && parm.getDescription().trim().length() > 0)
409 ttipText = (JvSwingUtils.wrapTooltip(true,
410 parm.getDescription() + (finfo != null ? "<br><img src=\""
411 + linkImageURL + "\"/>"
412 + MessageManager.getString(
413 "label.opt_and_params_further_details")
417 JvSwingUtils.addtoLayout(this, ttipText, new JLabel(parm.getName()),
419 updateControls(parm);
424 * Adds an 'expanded' format parameter, with any help shown in a panel that
425 * may be shown or hidden
429 private void addExpandableParam(ParameterI parm)
431 setPreferredSize(new Dimension(PARAM_WIDTH, PARAM_CLOSEDHEIGHT));
432 setBorder(new TitledBorder(parm.getName()));
434 descriptionText.setFont(new Font("Verdana", Font.PLAIN, 11));
435 descriptionText.setBackground(getBackground());
437 descriptionText.setEditable(false);
438 descPanel.getViewport().setView(descriptionText);
440 descPanel.setVisible(false);
442 JPanel firstrow = new JPanel();
443 firstrow.setLayout(null);
444 controlsPanel.setLayout(new BorderLayout());
445 controlsPanel.setBounds(new Rectangle(39, 10, PARAM_WIDTH - 70,
446 PARAM_CLOSEDHEIGHT - 50));
447 firstrow.add(controlsPanel);
448 firstrow.setBounds(new Rectangle(10, 20, PARAM_WIDTH - 30,
449 PARAM_CLOSEDHEIGHT - 30));
451 if (parm.getDescription() != null
452 && parm.getDescription().trim().length() > 0)
454 addExpandableHelp(firstrow, parm);
457 validator = parm.getValidValue();
459 if (validator != null)
461 isIntegerParameter = validator.getType() == ValueType.Integer;
465 if (parameter.getPossibleValues() != null)
467 isChoiceParameter = true;
470 updateControls(parm);
471 descPanel.setBounds(new Rectangle(10, PARAM_CLOSEDHEIGHT,
472 PARAM_WIDTH - 20, PARAM_HEIGHT - PARAM_CLOSEDHEIGHT - 5));
478 * Adds a button which can be clicked to show or hide help text
483 protected void addExpandableHelp(JPanel container, ParameterI param)
485 JButton showDescBtn = new JButton("+");
486 showDescBtn.setFont(new Font("Verdana", Font.PLAIN, 8));
489 descTooltip = JvSwingUtils.wrapTooltip(true,
490 MessageManager.formatMessage(
491 "label.opt_and_params_show_brief_desc_image_link",
493 { linkImageURL.toExternalForm() }));
494 showDescBtn.addMouseListener(this);
498 descTooltip = JvSwingUtils.wrapTooltip(true, MessageManager
499 .getString("label.opt_and_params_show_brief_desc"));
501 showDescBtn.setToolTipText(descTooltip);
502 showDescBtn.addActionListener(new ActionListener()
505 public void actionPerformed(ActionEvent e)
507 descriptionIsVisible = !descriptionIsVisible;
508 showDescBtn.setText(descriptionIsVisible ? "-" : "+");
509 showDescBtn.setToolTipText(
510 descriptionIsVisible ? null : descTooltip);
511 descPanel.setVisible(descriptionIsVisible);
512 descPanel.getVerticalScrollBar().setValue(0);
513 ParamBox.this.setPreferredSize(new Dimension(PARAM_WIDTH,
514 (descriptionIsVisible) ? PARAM_HEIGHT
515 : PARAM_CLOSEDHEIGHT));
516 ParamBox.this.validate();
517 pmdialogbox.refreshParamLayout();
520 descriptionText.setWrapStyleWord(true);
521 descriptionText.setLineWrap(true);
522 descriptionText.setColumns(32);
523 descriptionText.setText(param.getDescription());
524 showDescBtn.setBounds(new Rectangle(10, 10, 16, 16));
525 container.add(showDescBtn);
529 public void actionPerformed(ActionEvent e)
535 if (!isChoiceParameter)
537 updateSliderFromValueField();
543 * Checks whether the value of this parameter has been changed and notifies
544 * the parent page accordingly
546 private void checkIfModified()
548 Object newValue = updateSliderFromValueField();
549 boolean modified = true;
550 if (newValue.getClass() == lastVal.getClass())
552 modified = !newValue.equals(lastVal);
554 pmdialogbox.argSetModified(this, modified);
558 public int getBaseline(int width, int height)
564 // http://stackoverflow.com/questions/2743177/top-alignment-for-flowlayout
565 // helpful hint of using the Java 1.6 alignBaseLine property of FlowLayout
567 public Component.BaselineResizeBehavior getBaselineResizeBehavior()
569 return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
572 public ParameterI getParameter()
574 ParameterI prm = parameter.copy();
575 if (isChoiceParameter)
577 prm.setValue((String) choicebox.getSelectedItem());
581 prm.setValue(valueField.getText());
588 // reset the widget's initial value.
593 public void mouseClicked(MouseEvent e)
595 if (e.isPopupTrigger()) // for Windows
597 showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
602 public void mouseEntered(MouseEvent e)
607 public void mouseExited(MouseEvent e)
612 public void mousePressed(MouseEvent e)
614 if (e.isPopupTrigger()) // for Mac
616 showUrlPopUp(this, finfo.toString(), e.getX(), e.getY());
621 public void mouseReleased(MouseEvent e)
626 public void stateChanged(ChangeEvent e)
630 if (!isLogarithmicParameter)
633 * set (int or float formatted) text field value
635 valueField.setText(isIntegerParameter
636 ? String.valueOf(slider.getValue())
638 slider.getValue() / (float) sliderScaleFactor));
642 double value = Math.pow(Math.E,
643 slider.getValue() / (double) sliderScaleFactor);
644 valueField.setText(formatDouble(value));
651 * Answers the value formatted as a string to 3 decimal places - in
652 * scientific notation if the value is less than 0.001
657 String formatDouble(double value)
659 String format = value < 0.001 ? "%3.1E" : "%3.3f";
660 return String.format(format, value);
664 * Formats a number as integer or float (3dp) or scientific notation (1dp)
669 String formatNumber(Number n)
671 return n instanceof Integer ? String.valueOf(n.intValue())
672 : formatDouble(n.doubleValue());
675 void updateControls(ParameterI parm)
678 boolean init = (choicebox == null && valueField == null);
681 if (isChoiceParameter)
683 choicebox = new JComboBox<>();
684 choicebox.addActionListener(this);
685 controlsPanel.add(choicebox, BorderLayout.CENTER);
689 slider = new JSlider();
690 slider.addChangeListener(this);
691 int cols = parm instanceof StringParameter ? 20 : 0;
692 valueField = new JTextField(cols);
693 valueField.addActionListener(this);
694 valueField.addKeyListener(new KeyAdapter()
697 public void keyReleased(KeyEvent e)
701 if (valueField.getText().trim().length() > 0)
703 actionPerformed(null);
708 valueField.setPreferredSize(new Dimension(65, 25));
709 controlsPanel.add(slider, BorderLayout.WEST);
710 controlsPanel.add(valueField, BorderLayout.EAST);
716 if (isChoiceParameter)
720 for (String val : parm.getPossibleValues())
722 choicebox.addItem(val);
725 if (parm.getValue() != null)
727 choicebox.setSelectedItem(parm.getValue());
732 valueField.setText(parm.getValue());
735 lastVal = updateSliderFromValueField();
740 * Action depends on the type of the input parameter:
742 * <li>if a text input, returns the trimmed value</li>
743 * <li>if a choice list, returns the selected value</li>
744 * <li>if a value slider and input field, sets the value of the slider from
745 * the value in the text field, limiting it to any defined min-max
748 * Answers the (possibly modified) input value, as a String, Integer, Float
753 Object updateSliderFromValueField()
755 if (validator == null)
757 if (isChoiceParameter)
759 return choicebox.getSelectedItem();
761 slider.setVisible(false);
762 return valueField.getText().trim();
765 valueField.setText(valueField.getText().trim());
768 * ensure not outside min-max range
769 * TODO: provide some visual indicator if limit reached
773 valueField.setBackground(Color.WHITE);
774 double d = Double.parseDouble(valueField.getText());
775 if (validator.getMin() != null
776 && validator.getMin().doubleValue() > d)
778 valueField.setText(formatNumber(validator.getMin()));
780 if (validator.getMax() != null
781 && validator.getMax().doubleValue() < d)
783 valueField.setText(formatNumber(validator.getMax()));
785 } catch (NumberFormatException e)
787 valueField.setBackground(Color.yellow);
791 if (isIntegerParameter)
796 iVal = Integer.valueOf(valueField.getText());
797 } catch (Exception e)
799 valueField.setBackground(Color.yellow);
800 return Integer.valueOf(0);
803 if (validator.getMin() != null && validator.getMax() != null)
805 slider.getModel().setRangeProperties(iVal, 1,
806 validator.getMin().intValue(),
807 validator.getMax().intValue() + 1, true);
811 slider.setVisible(false);
813 return new Integer(iVal);
816 if (isLogarithmicParameter)
821 double eValue = Double.valueOf(valueField.getText());
822 dVal = Math.log(eValue) * sliderScaleFactor;
823 } catch (Exception e)
825 // shouldn't be possible here
826 valueField.setBackground(Color.yellow);
829 if (validator.getMin() != null && validator.getMax() != null)
831 double scaleMin = Math.log(validator.getMin().doubleValue())
833 double scaleMax = Math.log(validator.getMax().doubleValue())
835 slider.getModel().setRangeProperties((int) (dVal), 1,
836 (int) scaleMin, 1 + (int) scaleMax, true);
840 slider.setVisible(false);
842 return new Double(dVal);
848 fVal = Float.valueOf(valueField.getText());
849 } catch (Exception e)
851 return Float.valueOf(0f); // shouldn't happen
853 if (validator.getMin() != null && validator.getMax() != null)
855 float scaleMin = validator.getMin().floatValue()
857 float scaleMax = validator.getMax().floatValue()
859 slider.getModel().setRangeProperties(
860 (int) (fVal * sliderScaleFactor), 1, (int) scaleMin,
861 1 + (int) scaleMax, true);
865 slider.setVisible(false);
867 return new Float(fVal);
872 * Constructor with the option to show 'compact' format (parameter description
873 * as tooltip) or 'expanded' format (parameter description in a textbox which
874 * may be opened or closed). Use compact for simple description text, expanded
875 * for more wordy or formatted text.
877 * @param paramContainer
879 public OptsAndParamsPage(OptsParametersContainerI paramContainer,
882 poparent = paramContainer;
883 this.compact = compact;
886 public static void showUrlPopUp(JComponent invoker, final String finfo,
890 JPopupMenu mnu = new JPopupMenu();
891 JMenuItem mitem = new JMenuItem(
892 MessageManager.formatMessage("label.view_params", new String[]
894 mitem.addActionListener(new ActionListener()
898 public void actionPerformed(ActionEvent e)
900 Desktop.showUrl(finfo);
905 mnu.show(invoker, x, y);
908 public Map<String, OptionBox> getOptSet()
913 public void setOptSet(Map<String, OptionBox> optSet)
915 this.optSet = optSet;
918 public Map<String, ParamBox> getParamSet()
923 public void setParamSet(Map<String, ParamBox> paramSet)
925 this.paramSet = paramSet;
928 OptionBox addOption(OptionI opt)
930 OptionBox cb = optSet.get(opt.getName());
933 cb = new OptionBox(opt);
934 optSet.put(opt.getName(), cb);
935 // jobOptions.add(cb, FlowLayout.LEFT);
940 ParamBox addParameter(ParameterI arg)
942 ParamBox pb = paramSet.get(arg.getName());
945 pb = new ParamBox(poparent, arg);
946 paramSet.put(arg.getName(), pb);
947 // paramList.add(pb);
950 // take the defaults from the parameter
951 pb.updateControls(arg);
955 void selectOption(OptionI option, String string)
957 OptionBox cb = optSet.get(option.getName());
960 cb = addOption(option);
962 cb.enabled.setSelected(string != null); // initial state for an option.
965 if (option.getPossibleValues().contains(string))
967 cb.val.setSelectedItem(string);
971 throw new Error(String.format("Invalid value '%s' for option '%s'",
972 string, option.getName()));
975 if (option.isRequired() && !cb.enabled.isSelected())
977 // TODO: indicate paramset is not valid.. option needs to be selected!
979 cb.setInitialValue();
982 void setParameter(ParameterI arg)
984 ParamBox pb = paramSet.get(arg.getName());
991 pb.updateControls(arg);
997 * recover options and parameters from GUI
1001 public List<ArgumentI> getCurrentSettings()
1003 List<ArgumentI> argSet = new ArrayList<>();
1004 for (OptionBox opts : getOptSet().values())
1006 OptionI opt = opts.getOptionIfEnabled();
1012 for (ParamBox parambox : getParamSet().values())
1014 ParameterI parm = parambox.getParameter();