/* * 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. * * 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 . * The Jalview Authors are detailed in the 'AUTHORS' file. */ package jalview.gui; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; 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.FocusAdapter; import java.awt.event.FocusEvent; 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.List; import java.util.Map; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.border.TitledBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import jalview.util.MessageManager; 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 net.miginfocom.swing.MigLayout; /** * GUI generator/manager for options and parameters. Originally abstracted from * the WsJobParameters dialog box. * * @author jprocter * */ public class OptsAndParamsPage { /** * compact or verbose style parameters */ boolean compact = false; public class OptionBox extends JPanel implements MouseListener, ActionListener { JCheckBox enabled = new JCheckBox(); final URL finfo; boolean hasLink = false; boolean initEnabled = false; String initVal = null; OptionI option; JLabel optlabel = new JLabel(); JComboBox val = new JComboBox<>(); 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) { hasLink = true; enabled.setToolTipText(JvSwingUtils.wrapTooltip(true, ((desc == null || desc.trim().length() == 0) ? MessageManager.getString( "label.opt_and_params_further_details") : desc) + "
")); enabled.addMouseListener(this); } else { if (desc != null && desc.trim().length() > 0) { enabled.setToolTipText( JvSwingUtils.wrapTooltip(true, opt.getDescription())); } } add(enabled, BorderLayout.NORTH); for (String str : opt.getPossibleValues()) { val.addItem(str); } val.setSelectedItem(opt.getValue()); if (opt.getPossibleValues().size() > 1) { setLayout(new GridLayout(1, 2)); val.addActionListener(this); add(val, BorderLayout.SOUTH); } // 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(); } @Override public void actionPerformed(ActionEvent e) { if (e.getSource() != enabled) { enabled.setSelected(true); } checkIfModified(); } private void checkIfModified() { boolean notmod = (initEnabled == enabled.isSelected()); if (enabled.isSelected()) { if (initVal != null) { notmod &= initVal.equals(val.getSelectedItem()); } else { // compare against default service setting notmod &= option.getValue() == null || option.getValue().equals(val.getSelectedItem()); } } else { notmod &= (initVal != null) ? initVal.equals(val.getSelectedItem()) : val.getSelectedItem() != initVal; } poparent.argSetModified(this, !notmod); } public OptionI getOptionIfEnabled() { if (!enabled.isSelected()) { return null; } OptionI opt = option.copy(); if (opt.getPossibleValues() != null && opt.getPossibleValues().size() == 1) { // Hack to make sure the default value for an enabled option with only // one value is actually returned opt.setValue(opt.getPossibleValues().get(0)); } if (val.getSelectedItem() != null) { opt.setValue((String) val.getSelectedItem()); } else { if (option.getValue() != null) { opt.setValue(option.getValue()); } } return opt; } @Override public void mouseClicked(MouseEvent 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) { if (e.isPopupTrigger()) // Mac { showUrlPopUp(this, finfo.toString(), e.getX(), e.getY()); } } @Override public void mouseReleased(MouseEvent e) { } public void resetToDefault(boolean setDefaultParams) { enabled.setSelected(false); if (option.isRequired() || (setDefaultParams && option.getValue() != null)) { // Apply default value selectOption(option, option.getValue()); } } public void setInitialValue() { initEnabled = enabled.isSelected(); if (option.getPossibleValues() != null && option.getPossibleValues().size() > 1) { initVal = (String) val.getSelectedItem(); } else { initVal = (initEnabled) ? (String) val.getSelectedItem() : null; } } } public class ParamBox extends JPanel implements ChangeListener, ActionListener, MouseListener { boolean adjusting = false; boolean choice = false; JComboBox choicebox; JPanel controlPanel = new JPanel(); boolean descisvisible = false; JScrollPane descPanel = new JScrollPane(); final URL finfo; boolean integ = false; String lastVal; ParameterI parameter; final OptsParametersContainerI pmdialogbox; JPanel settingPanel = new JPanel(); JButton showDesc = new JButton(); Slider slider = null; JTextArea string = new JTextArea(); ValueConstrainI validator = null; JTextField valueField = null; public ParamBox(final OptsParametersContainerI pmlayout, ParameterI parm) { pmdialogbox = pmlayout; finfo = parm.getFurtherDetails(); validator = parm.getValidValue(); parameter = parm; if (validator != null) { integ = validator.getType() == ValueType.Integer; } else { if (parameter.getPossibleValues() != null) { choice = true; } } if (!compact) { makeExpanderParam(parm); } else { makeCompactParam(parm); } } private void makeCompactParam(ParameterI parm) { setLayout(new MigLayout("", "[][grow]")); String ttipText = null; controlPanel.setLayout(new BorderLayout()); if (parm.getDescription() != null && parm.getDescription().trim().length() > 0) { // Only create description boxes if there actually is a description. ttipText = (JvSwingUtils.wrapTooltip(true, parm.getDescription() + (finfo != null ? "
" + MessageManager.getString( "label.opt_and_params_further_details") : ""))); } JvSwingUtils.mgAddtoLayout(this, ttipText, new JLabel(parm.getName()), controlPanel, ""); updateControls(parm); validate(); } private void makeExpanderParam(final 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()); string.setEditable(false); descPanel.getViewport().setView(string); descPanel.setVisible(false); JPanel firstrow = new JPanel(); firstrow.setLayout(null); controlPanel.setLayout(new BorderLayout()); controlPanel.setBounds(new Rectangle(39, 10, PARAM_WIDTH - 70, PARAM_CLOSEDHEIGHT - 50)); firstrow.add(controlPanel); 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(JvSwingUtils.wrapTooltip(true, MessageManager.formatMessage( "label.opt_and_params_show_brief_desc_image_link", new String[] { linkImageURL.toExternalForm() }))); showDesc.addMouseListener(this); } else { showDesc.setToolTipText( JvSwingUtils.wrapTooltip(true, MessageManager.getString( "label.opt_and_params_show_brief_desc"))); } showDesc.addActionListener(new ActionListener() { @Override 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); } add(firstrow); validator = parm.getValidValue(); parameter = parm; if (validator != null) { integ = validator.getType() == ValueType.Integer; } else { if (parameter.getPossibleValues() != null) { choice = true; } } updateControls(parm); descPanel.setBounds(new Rectangle(10, PARAM_CLOSEDHEIGHT, PARAM_WIDTH - 20, PARAM_HEIGHT - PARAM_CLOSEDHEIGHT - 5)); add(descPanel); validate(); } /** * Action on input in text field */ @Override public void actionPerformed(ActionEvent e) { if (adjusting) { return; } if (!choice) { updateSliderFromValueField(); } checkIfModified(); } private void checkIfModified() { Object cstate = getCurrentValue(); boolean modified = !cstate.equals(lastVal); pmdialogbox.argSetModified(this, modified); } /** * Answers the current value of the parameter, as text * * @return */ private String getCurrentValue() { return choice ? (String) choicebox.getSelectedItem() : valueField.getText(); } @Override public int getBaseline(int width, int height) { return 0; } // from // http://stackoverflow.com/questions/2743177/top-alignment-for-flowlayout // helpful hint of using the Java 1.6 alignBaseLine property of FlowLayout @Override public Component.BaselineResizeBehavior getBaselineResizeBehavior() { return Component.BaselineResizeBehavior.CONSTANT_ASCENT; } public int getBoxHeight() { return (descisvisible ? PARAM_HEIGHT : PARAM_CLOSEDHEIGHT); } public ParameterI getParameter() { ParameterI prm = parameter.copy(); if (choice) { prm.setValue((String) choicebox.getSelectedItem()); } else { prm.setValue(valueField.getText()); } return prm; } public void init() { // reset the widget's initial value. lastVal = null; } @Override public void mouseClicked(MouseEvent 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) { if (e.isPopupTrigger()) // for Mac { showUrlPopUp(this, finfo.toString(), e.getX(), e.getY()); } } @Override public void mouseReleased(MouseEvent e) { // TODO Auto-generated method stub } /** * Action on change of slider value */ @Override public void stateChanged(ChangeEvent e) { if (!adjusting) { float value = slider.getSliderValue(); valueField.setText(integ ? Integer.toString((int) value) : Float.toString(value)); checkIfModified(); } } public void updateControls(ParameterI parm) { adjusting = true; boolean init = (choicebox == null && valueField == null); if (init) { if (choice) { choicebox = new JComboBox<>(); choicebox.addActionListener(this); controlPanel.add(choicebox, BorderLayout.CENTER); } else { valueField = new JTextField(); valueField.addActionListener(this); valueField.addKeyListener(new KeyListener() { @Override public void keyTyped(KeyEvent e) { } @Override public void keyReleased(KeyEvent e) { if (e.isActionKey()) { if (valueField.getText().trim().length() > 0) { actionPerformed(null); } } } @Override public void keyPressed(KeyEvent e) { } }); valueField.addFocusListener(new FocusAdapter() { @Override public void focusLost(FocusEvent e) { actionPerformed(null); } }); valueField.setPreferredSize(new Dimension(60, 25)); valueField.setText(parm.getValue()); slider = makeSlider(parm.getValidValue()); updateSliderFromValueField(); slider.addChangeListener(this); controlPanel.add(slider, BorderLayout.WEST); controlPanel.add(valueField, BorderLayout.EAST); } } if (parm != null) { if (choice) { if (init) { List vals = parm.getPossibleValues(); for (String val : vals) { choicebox.addItem(val); } } if (parm.getValue() != null) { choicebox.setSelectedItem(parm.getValue()); } } else { valueField.setText(parm.getValue()); } } lastVal = getCurrentValue(); adjusting = false; } private Slider makeSlider(ValueConstrainI validValue) { if (validValue != null) { final Number minValue = validValue.getMin(); final Number maxValue = validValue.getMax(); if (minValue != null && maxValue != null) { return new Slider(minValue.floatValue(), maxValue.floatValue(), minValue.floatValue()); } } /* * otherwise, a nominal slider which will not be visible */ return new Slider(0, 100, 50); } public void updateSliderFromValueField() { if (validator != null) { final Number minValue = validator.getMin(); final Number maxValue = validator.getMax(); if (integ) { int iVal = 0; try { valueField.setText(valueField.getText().trim()); iVal = Integer.valueOf(valueField.getText()); if (minValue != null && minValue.intValue() > iVal) { iVal = minValue.intValue(); // TODO: provide visual indication that hard limit was reached for // this parameter } if (maxValue != null && maxValue.intValue() < iVal) { iVal = maxValue.intValue(); } } catch (NumberFormatException e) { jalview.bin.Console.errPrintln(e.toString()); } if (minValue != null || maxValue != null) { valueField.setText(String.valueOf(iVal)); slider.setSliderValue(iVal); } else { slider.setVisible(false); } } else { float fVal = 0f; try { valueField.setText(valueField.getText().trim()); fVal = Float.valueOf(valueField.getText()); if (minValue != null && minValue.floatValue() > fVal) { fVal = minValue.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 (maxValue != null && maxValue.floatValue() < fVal) { fVal = maxValue.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 (NumberFormatException e) { jalview.bin.Console.errPrintln(e.toString()); } if (minValue != null && maxValue != null) { slider.setSliderModel(minValue.floatValue(), maxValue.floatValue(), fVal); } else { slider.setVisible(false); } } } else { if (!choice) { slider.setVisible(false); } } } } 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); } public OptsAndParamsPage(OptsParametersContainerI paramContainer, boolean compact) { poparent = paramContainer; this.compact = compact; } public static void showUrlPopUp(JComponent invoker, final String finfo, int x, int y) { JPopupMenu mnu = new JPopupMenu(); JMenuItem mitem = new JMenuItem( MessageManager.formatMessage("label.view_params", new String[] { finfo })); mitem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Desktop.showUrl(finfo); } }); mnu.add(mitem); mnu.show(invoker, x, y); } URL linkImageURL = getClass().getResource("/images/link.gif"); Map optSet = new java.util.LinkedHashMap<>(); Map paramSet = new java.util.LinkedHashMap<>(); public Map getOptSet() { return optSet; } public void setOptSet(Map optSet) { this.optSet = optSet; } public Map getParamSet() { return paramSet; } public void setParamSet(Map paramSet) { this.paramSet = paramSet; } OptsParametersContainerI poparent; OptionBox addOption(OptionI opt) { OptionBox cb = optSet.get(opt.getName()); if (cb == null) { cb = new OptionBox(opt); optSet.put(opt.getName(), cb); // jobOptions.add(cb, FlowLayout.LEFT); } return cb; } ParamBox addParameter(ParameterI arg) { ParamBox pb = paramSet.get(arg.getName()); if (pb == null) { pb = new ParamBox(poparent, arg); paramSet.put(arg.getName(), pb); // paramList.add(pb); } pb.init(); // take the defaults from the parameter pb.updateControls(arg); return pb; } void selectOption(OptionI option, String string) { OptionBox cb = optSet.get(option.getName()); if (cb == null) { cb = addOption(option); } cb.enabled.setSelected(string != null); // initial state for an option. if (string != null) { if (option.getPossibleValues().contains(string)) { cb.val.setSelectedItem(string); } else { throw new Error(MessageManager.formatMessage( "error.invalid_value_for_option", new String[] { string, option.getName() })); } } if (option.isRequired() && !cb.enabled.isSelected()) { // TODO: indicate paramset is not valid.. option needs to be selected! } cb.setInitialValue(); } void setParameter(ParameterI arg) { ParamBox pb = paramSet.get(arg.getName()); if (pb == null) { addParameter(arg); } else { pb.updateControls(arg); } } /** * recover options and parameters from GUI * * @return */ public List getCurrentSettings() { List argSet = new ArrayList<>(); for (OptionBox opts : getOptSet().values()) { OptionI opt = opts.getOptionIfEnabled(); if (opt != null) { argSet.add(opt); } } for (ParamBox parambox : getParamSet().values()) { ParameterI parm = parambox.getParameter(); if (parm != null) { argSet.add(parm); } } return argSet; } }