X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fgui%2FFeatureTypeSettings.java;h=09bb2a3c7a27a972b7ba931c8885d19b47ac1dd3;hb=57738a1f3c19b1c3a00bd3ac5108f8cd0af32f99;hp=372234adb25d285582a8ddaa023a53d3f7f64a79;hpb=8690631b5afe2db0a06ef1b074d2633598c403ae;p=jalview.git diff --git a/src/jalview/gui/FeatureTypeSettings.java b/src/jalview/gui/FeatureTypeSettings.java index 372234a..09bb2a3 100644 --- a/src/jalview/gui/FeatureTypeSettings.java +++ b/src/jalview/gui/FeatureTypeSettings.java @@ -20,25 +20,28 @@ */ package jalview.gui; +import jalview.api.AlignViewportI; import jalview.api.AlignmentViewPanel; import jalview.api.FeatureColourI; +import jalview.bin.Console; import jalview.datamodel.GraphLine; import jalview.datamodel.features.FeatureAttributes; import jalview.datamodel.features.FeatureAttributes.Datatype; +import jalview.datamodel.features.FeatureMatcher; +import jalview.datamodel.features.FeatureMatcherI; +import jalview.datamodel.features.FeatureMatcherSet; +import jalview.datamodel.features.FeatureMatcherSetI; +import jalview.gui.JalviewColourChooser.ColourChooserListener; import jalview.schemes.FeatureColour; import jalview.util.ColorUtils; import jalview.util.MessageManager; import jalview.util.matcher.Condition; -import jalview.util.matcher.KeyedMatcher; -import jalview.util.matcher.KeyedMatcherI; -import jalview.util.matcher.KeyedMatcherSet; -import jalview.util.matcher.KeyedMatcherSetI; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; -import java.awt.LayoutManager; +import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusAdapter; @@ -47,6 +50,9 @@ import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.math.BigDecimal; +import java.math.MathContext; +import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; @@ -55,19 +61,15 @@ import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JCheckBox; -import javax.swing.JColorChooser; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; -import javax.swing.JSlider; -import javax.swing.JTabbedPane; import javax.swing.JTextField; -import javax.swing.SwingConstants; +import javax.swing.border.EmptyBorder; import javax.swing.border.LineBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; -import javax.swing.plaf.basic.BasicArrowButton; /** * A dialog where the user can configure colour scheme, and any filters, for one @@ -78,6 +80,14 @@ import javax.swing.plaf.basic.BasicArrowButton; */ public class FeatureTypeSettings extends JalviewDialog { + private final static MathContext FOUR_SIG_FIG = new MathContext(4); + + private final static String LABEL_18N = MessageManager + .getString("label.label"); + + private final static String SCORE_18N = MessageManager + .getString("label.score"); + private static final int RADIO_WIDTH = 130; private static final String COLON = ":"; @@ -94,58 +104,71 @@ public class FeatureTypeSettings extends JalviewDialog private static final int BELOW_THRESHOLD_OPTION = 2; + private static final DecimalFormat DECFMT_2_2 = new DecimalFormat( + "##.##"); + /* * FeatureRenderer holds colour scheme and filters for feature types */ - private final FeatureRenderer fr; // todo refactor to allow interface type here + private final FeatureRenderer fr; // todo refactor to allow interface type + // here /* * the view panel to update when settings change */ - private final AlignmentViewPanel ap; + final AlignmentViewPanel ap; - private final String featureType; + final String featureType; /* * the colour and filters to reset to on Cancel */ private final FeatureColourI originalColour; - private final KeyedMatcherSetI originalFilter; + private final FeatureMatcherSetI originalFilter; /* * set flag to true when setting values programmatically, * to avoid invocation of action handlers */ - private boolean adjusting = false; + boolean adjusting = false; + /* + * minimum of the value range for graduated colour + * (may be for feature score or for a numeric attribute) + */ private float min; + /* + * maximum of the value range for graduated colour + */ private float max; - private float scaleFactor; - /* * radio button group, to select what to colour by: * simple colour, by category (text), or graduated */ - private JRadioButton simpleColour = new JRadioButton(); + JRadioButton simpleColour = new JRadioButton(); - private JRadioButton byCategory = new JRadioButton(); + JRadioButton byCategory = new JRadioButton(); - private JRadioButton graduatedColour = new JRadioButton(); + JRadioButton graduatedColour = new JRadioButton(); - private JPanel singleColour = new JPanel(); + JPanel coloursPanel; - private JPanel minColour = new JPanel(); + JPanel filtersPanel; - private JPanel maxColour = new JPanel(); + JPanel singleColour = new JPanel(); - private JComboBox threshold = new JComboBox<>(); + JPanel minColour = new JPanel(); - private JSlider slider = new JSlider(); + JPanel maxColour = new JPanel(); - private JTextField thresholdValue = new JTextField(20); + private JComboBox threshold = new JComboBox<>(); + + private Slider slider; + + JTextField thresholdValue = new JTextField(20); private JCheckBox thresholdIsMin = new JCheckBox(); @@ -158,17 +181,17 @@ public class FeatureTypeSettings extends JalviewDialog /* * choice of option for 'colour for no value' */ - private JComboBox noValueCombo; + private JComboBox noValueCombo; /* * choice of what to colour by text (Label or attribute) */ - private JComboBox colourByTextCombo; + private JComboBox colourByTextCombo; /* * choice of what to colour by range (Score or attribute) */ - private JComboBox colourByRangeCombo; + private JComboBox colourByRangeCombo; private JRadioButton andFilters; @@ -177,15 +200,10 @@ public class FeatureTypeSettings extends JalviewDialog /* * filters for the currently selected feature type */ - private List filters; - - // set white normally, black to debug layout - private Color debugBorderColour = Color.white; + List filters; private JPanel chooseFiltersPanel; - private JTabbedPane tabbedPane; - /** * Constructor * @@ -194,20 +212,6 @@ public class FeatureTypeSettings extends JalviewDialog */ public FeatureTypeSettings(FeatureRenderer frender, String theType) { - this(frender, false, theType); - } - - /** - * Constructor, with option to make a blocking dialog (has to complete in the - * AWT event queue thread). Currently this option is always set to false. - * - * @param frender - * @param blocking - * @param theType - */ - FeatureTypeSettings(FeatureRenderer frender, boolean blocking, - String theType) - { this.fr = frender; this.featureType = theType; ap = fr.ap; @@ -225,9 +229,9 @@ public class FeatureTypeSettings extends JalviewDialog return; } - updateColoursTab(); + updateColoursPanel(); - updateFiltersTab(); + updateFiltersPanel(); adjusting = false; @@ -236,16 +240,15 @@ public class FeatureTypeSettings extends JalviewDialog String title = MessageManager .formatMessage("label.display_settings_for", new String[] { theType }); - initDialogFrame(this, true, blocking, title, 600, 360); - + initDialogFrame(this, true, false, title, 580, 500); waitForInput(); } /** - * Configures the widgets on the Colours tab according to the current feature - * colour scheme + * Configures the widgets on the Colours panel according to the current + * feature colour scheme */ - private void updateColoursTab() + private void updateColoursPanel() { FeatureColourI fc = fr.getFeatureColours().get(featureType); @@ -260,9 +263,9 @@ public class FeatureTypeSettings extends JalviewDialog */ if (fc.isSimpleColour()) { - simpleColour.setSelected(true); singleColour.setBackground(fc.getColour()); singleColour.setForeground(fc.getColour()); + simpleColour.setSelected(true); } /* @@ -275,13 +278,12 @@ public class FeatureTypeSettings extends JalviewDialog if (fc.isColourByAttribute()) { String[] attributeName = fc.getAttributeName(); - colourByTextCombo - .setSelectedItem(toAttributeDisplayName(attributeName)); + colourByTextCombo.setSelectedItem( + FeatureMatcher.toAttributeDisplayName(attributeName)); } else { - colourByTextCombo - .setSelectedItem(MessageManager.getString("label.label")); + colourByTextCombo.setSelectedItem(LABEL_18N); } } else @@ -306,6 +308,7 @@ public class FeatureTypeSettings extends JalviewDialog * Graduated colour, by score or attribute value range */ graduatedColour.setSelected(true); + updateColourMinMax(); // ensure min, max are set colourByRangeCombo.setEnabled(colourByRangeCombo.getItemCount() > 1); minColour.setEnabled(true); maxColour.setEnabled(true); @@ -317,13 +320,12 @@ public class FeatureTypeSettings extends JalviewDialog if (fc.isColourByAttribute()) { String[] attributeName = fc.getAttributeName(); - colourByRangeCombo - .setSelectedItem(toAttributeDisplayName(attributeName)); + colourByRangeCombo.setSelectedItem( + FeatureMatcher.toAttributeDisplayName(attributeName)); } else { - colourByRangeCombo - .setSelectedItem(MessageManager.getString("label.score")); + colourByRangeCombo.setSelectedItem(SCORE_18N); } Color noColour = fc.getNoColour(); if (noColour == null) @@ -343,12 +345,11 @@ public class FeatureTypeSettings extends JalviewDialog * update min-max scaling if there is a range to work with, * else disable the widgets (this shouldn't happen if only * valid options are offered in the combo box) + * offset slider to have only non-negative values if necessary (JAL-2983) */ - scaleFactor = (max == min) ? 1f : 100f / (max - min); - float range = (max - min) * scaleFactor; - slider.setMinimum((int) (min * scaleFactor)); - slider.setMaximum((int) (max * scaleFactor)); - slider.setMajorTickSpacing((int) (range / 10f)); + slider.setSliderModel(min, max, min); + slider.setMajorTickSpacing( + (int) ((slider.getMaximum() - slider.getMinimum()) / 10f)); threshline = new GraphLine((max - min) / 2f, "Threshold", Color.black); @@ -360,8 +361,8 @@ public class FeatureTypeSettings extends JalviewDialog fc.isAboveThreshold() ? ABOVE_THRESHOLD_OPTION : BELOW_THRESHOLD_OPTION); slider.setEnabled(true); - slider.setValue((int) (fc.getThreshold() * scaleFactor)); - thresholdValue.setText(String.valueOf(getRoundedSliderValue())); + slider.setSliderValue(fc.getThreshold()); + setThresholdValueText(fc.getThreshold()); thresholdValue.setEnabled(true); thresholdIsMin.setEnabled(true); } @@ -384,8 +385,6 @@ public class FeatureTypeSettings extends JalviewDialog private void initialise() { this.setLayout(new BorderLayout()); - tabbedPane = new JTabbedPane(); - this.add(tabbedPane, BorderLayout.CENTER); /* * an ActionListener that applies colour changes @@ -400,18 +399,16 @@ public class FeatureTypeSettings extends JalviewDialog }; /* - * first tab: colour options + * first panel: colour options */ JPanel coloursPanel = initialiseColoursPanel(); - tabbedPane.addTab(MessageManager.getString("action.colour"), - coloursPanel); + this.add(coloursPanel, BorderLayout.NORTH); /* - * second tab: filter options + * second panel: filter options */ JPanel filtersPanel = initialiseFiltersPanel(); - tabbedPane.addTab(MessageManager.getString("label.filters"), - filtersPanel); + this.add(filtersPanel, BorderLayout.CENTER); JPanel okCancelPanel = initialiseOkCancelPanel(); @@ -422,32 +419,47 @@ public class FeatureTypeSettings extends JalviewDialog * Updates the min-max range if Colour By selected item is Score, or an * attribute, with a min-max range */ - protected void updateMinMax() + protected void updateColourMinMax() { if (!graduatedColour.isSelected()) { return; } - float[] minMax = null; String colourBy = (String) colourByRangeCombo.getSelectedItem(); - if (MessageManager.getString("label.score").equals(colourBy)) + float[] minMax = getMinMax(colourBy); + + if (minMax != null) + { + min = minMax[0]; + max = minMax[1]; + } + } + + /** + * Retrieves the min-max range: + *
    + *
  • of feature score, if colour or filter is by Score
  • + *
  • else of the selected attribute
  • + *
+ * + * @param attName + * @return + */ + private float[] getMinMax(String attName) + { + float[] minMax = null; + if (SCORE_18N.equals(attName)) { minMax = fr.getMinMax().get(featureType)[0]; } else { // colour by attribute range - String[] attNames = fromAttributeDisplayName(colourBy); minMax = FeatureAttributes.getInstance().getMinMax(featureType, - attNames); - } - - if (minMax != null) - { - min = minMax[0]; - max = minMax[1]; + FeatureMatcher.fromAttributeDisplayName(attName)); } + return minMax; } /** @@ -473,6 +485,7 @@ public class FeatureTypeSettings extends JalviewDialog graduatedColour = new JRadioButton( MessageManager.getString("label.by_range_of") + COLON); graduatedColour.setPreferredSize(new Dimension(RADIO_WIDTH, 20)); + graduatedColour.setOpaque(false); graduatedColour.addItemListener(new ItemListener() { @Override @@ -524,7 +537,9 @@ public class FeatureTypeSettings extends JalviewDialog { if (minColour.isEnabled()) { - showColourChooser(minColour, "label.select_colour_minimum_value"); + String ttl = MessageManager + .getString("label.select_colour_minimum_value"); + showColourChooser(minColour, ttl); } } }); @@ -540,21 +555,29 @@ public class FeatureTypeSettings extends JalviewDialog { if (maxColour.isEnabled()) { - showColourChooser(maxColour, "label.select_colour_maximum_value"); + String ttl = MessageManager + .getString("label.select_colour_maximum_value"); + showColourChooser(maxColour, ttl); } } }); maxColour.setBorder(new LineBorder(Color.black)); /* - * default max colour to current colour (if a plain colour), - * or to Black if colour by label; make min colour a pale - * version of max colour + * if not set, default max colour to last plain colour, + * and make min colour a pale version of max colour */ - FeatureColourI fc = fr.getFeatureColours().get(featureType); - Color bg = fc.isSimpleColour() ? fc.getColour() : Color.BLACK; - maxColour.setBackground(bg); - minColour.setBackground(ColorUtils.bleachColour(bg, 0.9f)); + Color max = originalColour.getMaxColour(); + if (max == null) + { + max = originalColour.getColour(); + minColour.setBackground(ColorUtils.bleachColour(max, 0.9f)); + } + else + { + maxColour.setBackground(max); + minColour.setBackground(originalColour.getMinColour()); + } noValueCombo = new JComboBox<>(); noValueCombo.addItem(MessageManager.getString("label.no_colour")); @@ -619,6 +642,7 @@ public class FeatureTypeSettings extends JalviewDialog thresholdValue_actionPerformed(); } }); + slider = new Slider(0f, 100f, 50f); slider.setPaintLabels(false); slider.setPaintTicks(true); slider.setBackground(Color.white); @@ -635,8 +659,8 @@ public class FeatureTypeSettings extends JalviewDialog { if (!adjusting) { - thresholdValue - .setText(String.valueOf(slider.getValue() / scaleFactor)); + setThresholdValueText(slider.getSliderValue()); + thresholdValue.setBackground(Color.white); // to reset red for invalid sliderValueChanged(); } } @@ -652,7 +676,7 @@ public class FeatureTypeSettings extends JalviewDialog */ if (ap != null) { - ap.paintAlignment(true, true); + refreshDisplay(true); } } }); @@ -702,20 +726,22 @@ public class FeatureTypeSettings extends JalviewDialog private JPanel initialiseColoursPanel() { JPanel colourByPanel = new JPanel(); + colourByPanel.setBackground(Color.white); colourByPanel.setLayout(new BoxLayout(colourByPanel, BoxLayout.Y_AXIS)); + JvSwingUtils.createTitledBorder(colourByPanel, + MessageManager.getString("action.colour"), true); /* * simple colour radio button and colour picker */ JPanel simpleColourPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); simpleColourPanel.setBackground(Color.white); - JvSwingUtils.createTitledBorder(simpleColourPanel, - MessageManager.getString("label.simple"), true); colourByPanel.add(simpleColourPanel); simpleColour = new JRadioButton( MessageManager.getString("label.simple_colour")); simpleColour.setPreferredSize(new Dimension(RADIO_WIDTH, 20)); + simpleColour.setOpaque(false); simpleColour.addItemListener(new ItemListener() { @Override @@ -723,15 +749,24 @@ public class FeatureTypeSettings extends JalviewDialog { if (simpleColour.isSelected() && !adjusting) { - showColourChooser(singleColour, "label.select_colour"); + colourChanged(true); } } - }); - + singleColour.setFont(JvSwingUtils.getLabelFont()); singleColour.setBorder(BorderFactory.createLineBorder(Color.black)); singleColour.setPreferredSize(new Dimension(40, 20)); + // if (originalColour.isGraduatedColour()) + // { + // singleColour.setBackground(originalColour.getMaxColour()); + // singleColour.setForeground(originalColour.getMaxColour()); + // } + // else + // { + singleColour.setBackground(originalColour.getColour()); + singleColour.setForeground(originalColour.getColour()); + // } singleColour.addMouseListener(new MouseAdapter() { @Override @@ -739,7 +774,9 @@ public class FeatureTypeSettings extends JalviewDialog { if (simpleColour.isSelected()) { - showColourChooser(singleColour, "label.select_colour"); + String ttl = MessageManager + .formatMessage("label.select_colour_for", featureType); + showColourChooser(singleColour, ttl); } } }); @@ -757,6 +794,7 @@ public class FeatureTypeSettings extends JalviewDialog byCategory = new JRadioButton( MessageManager.getString("label.by_text_of") + COLON); byCategory.setPreferredSize(new Dimension(RADIO_WIDTH, 20)); + byCategory.setOpaque(false); byCategory.addItemListener(new ItemListener() { @Override @@ -801,23 +839,35 @@ public class FeatureTypeSettings extends JalviewDialog return colourByPanel; } - private void showColourChooser(JPanel colourPanel, String key) + /** + * Shows a colour chooser dialog, and if a selection is made, updates the + * colour of the given panel + * + * @param colourPanel + * the panel whose background colour is being picked + * @param title + */ + void showColourChooser(JPanel colourPanel, String title) { - Color col = JColorChooser.showDialog(this, - MessageManager.getString(key), colourPanel.getBackground()); - if (col != null) + ColourChooserListener listener = new ColourChooserListener() { - colourPanel.setBackground(col); - colourPanel.setForeground(col); - } - colourPanel.repaint(); - colourChanged(true); + @Override + public void colourSelected(Color col) + { + colourPanel.setBackground(col); + colourPanel.setForeground(col); + colourPanel.repaint(); + colourChanged(true); + } + }; + JalviewColourChooser.showColourChooser(this, title, + colourPanel.getBackground(), listener); } /** - * Constructs and sets the selected colour options as the colour for the feature - * type, and repaints the alignment, and optionally the Overview and/or - * structure viewer if open + * Constructs and sets the selected colour options as the colour for the + * feature type, and repaints the alignment, and optionally the Overview + * and/or structure viewer if open * * @param updateStructsAndOverview */ @@ -835,7 +885,7 @@ public class FeatureTypeSettings extends JalviewDialog * ensure min-max range is for the latest choice of * 'graduated colour by' */ - updateMinMax(); + updateColourMinMax(); FeatureColourI acg = makeColourFromInputs(); @@ -843,9 +893,9 @@ public class FeatureTypeSettings extends JalviewDialog * save the colour, and repaint stuff */ fr.setColour(featureType, acg); - ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview); + refreshDisplay(updateStructsAndOverview); - updateColoursTab(); + updateColoursPanel(); } /** @@ -856,41 +906,9 @@ public class FeatureTypeSettings extends JalviewDialog private FeatureColourI makeColourFromInputs() { /* - * easiest case - a single colour - */ - if (simpleColour.isSelected()) - { - return new FeatureColour(singleColour.getBackground()); - } - - /* - * next easiest case - colour by Label, or attribute text - */ - if (byCategory.isSelected()) - { - Color c = this.getBackground(); - FeatureColourI fc = new FeatureColour(c, c, null, 0f, 0f); - fc.setColourByLabel(true); - String byWhat = (String) colourByTextCombo.getSelectedItem(); - if (!MessageManager.getString("label.label").equals(byWhat)) - { - fc.setAttributeName(fromAttributeDisplayName(byWhat)); - } - return fc; - } - - /* - * remaining case - graduated colour by score, or attribute value + * min-max range is to (or from) threshold value if + * 'threshold is min/max' is selected */ - Color noColour = null; - if (noValueCombo.getSelectedIndex() == MIN_COLOUR_OPTION) - { - noColour = minColour.getBackground(); - } - else if (noValueCombo.getSelectedIndex() == MAX_COLOUR_OPTION) - { - noColour = maxColour.getBackground(); - } float thresh = 0f; try @@ -900,14 +918,9 @@ public class FeatureTypeSettings extends JalviewDialog { // invalid inputs are already handled on entry } - - /* - * min-max range is to (or from) threshold value if - * 'threshold is min/max' is selected - */ float minValue = min; float maxValue = max; - final int thresholdOption = threshold.getSelectedIndex(); + int thresholdOption = threshold.getSelectedIndex(); if (thresholdIsMin.isSelected() && thresholdOption == ABOVE_THRESHOLD_OPTION) { @@ -918,20 +931,56 @@ public class FeatureTypeSettings extends JalviewDialog { maxValue = thresh; } + Color noColour = null; + if (noValueCombo.getSelectedIndex() == MIN_COLOUR_OPTION) + { + noColour = minColour.getBackground(); + } + else if (noValueCombo.getSelectedIndex() == MAX_COLOUR_OPTION) + { + noColour = maxColour.getBackground(); + } /* - * make the graduated colour + * construct a colour that 'remembers' all the options, including + * those not currently selected */ - FeatureColourI fc = new FeatureColour(minColour.getBackground(), - maxColour.getBackground(), noColour, minValue, maxValue); + FeatureColourI fc = new FeatureColour(singleColour.getBackground(), + minColour.getBackground(), maxColour.getBackground(), noColour, + minValue, maxValue); + + /* + * easiest case - a single colour + */ + if (simpleColour.isSelected()) + { + ((FeatureColour) fc).setGraduatedColour(false); + return fc; + } /* + * next easiest case - colour by Label, or attribute text + */ + if (byCategory.isSelected()) + { + fc.setColourByLabel(true); + String byWhat = (String) colourByTextCombo.getSelectedItem(); + if (!LABEL_18N.equals(byWhat)) + { + fc.setAttributeName( + FeatureMatcher.fromAttributeDisplayName(byWhat)); + } + return fc; + } + + /* + * remaining case - graduated colour by score, or attribute value; * set attribute to colour by if selected */ String byWhat = (String) colourByRangeCombo.getSelectedItem(); - if (!MessageManager.getString("label.score").equals(byWhat)) + if (!SCORE_18N.equals(byWhat)) { - fc.setAttributeName(fromAttributeDisplayName(byWhat)); + fc.setAttributeName(FeatureMatcher.fromAttributeDisplayName(byWhat)); } /* @@ -956,30 +1005,6 @@ public class FeatureTypeSettings extends JalviewDialog return fc; } - /** - * A helper method that converts a 'compound' attribute name from its display - * form, e.g. CSQ:PolyPhen to array form, e.g. { "CSQ", "PolyPhen" } - * - * @param attribute - * @return - */ - private String[] fromAttributeDisplayName(String attribute) - { - return attribute == null ? null : attribute.split(COLON); - } - - /** - * A helper method that converts a 'compound' attribute name to its display - * form, e.g. CSQ:PolyPhen from its array form, e.g. { "CSQ", "PolyPhen" } - * - * @param attName - * @return - */ - private String toAttributeDisplayName(String[] attName) - { - return attName == null ? "" : String.join(COLON, attName); - } - @Override protected void raiseClosed() { @@ -1007,7 +1032,7 @@ public class FeatureTypeSettings extends JalviewDialog { fr.setColour(featureType, originalColour); fr.setFeatureFilter(featureType, originalFilter); - ap.paintAlignment(true, true); + refreshDisplay(true); } /** @@ -1017,33 +1042,48 @@ public class FeatureTypeSettings extends JalviewDialog { try { + /* + * set 'adjusting' flag while moving the slider, so it + * doesn't then in turn change the value (with rounding) + */ adjusting = true; float f = Float.parseFloat(thresholdValue.getText()); - slider.setValue((int) (f * scaleFactor)); + f = Float.max(f, this.min); + f = Float.min(f, this.max); + setThresholdValueText(f); + slider.setSliderValue(f); threshline.value = f; thresholdValue.setBackground(Color.white); // ok - - /* - * force repaint of any Overview window or structure - */ - ap.paintAlignment(true, true); + adjusting = false; + colourChanged(true); } catch (NumberFormatException ex) { thresholdValue.setBackground(Color.red); // not ok - } finally - { adjusting = false; } } /** + * Sets the text field for threshold value, rounded to four significant + * figures + * + * @param f + */ + void setThresholdValueText(float f) + { + BigDecimal formatted = new BigDecimal(f).round(FOUR_SIG_FIG) + .stripTrailingZeros(); + thresholdValue.setText(formatted.toPlainString()); + } + + /** * Action on change of threshold slider value. This may be done interactively * (by moving the slider), or programmatically (to update the slider after * manual input of a threshold value). */ protected void sliderValueChanged() { - threshline.value = getRoundedSliderValue(); + threshline.value = slider.getSliderValue(); /* * repaint alignment, but not Overview or structure, @@ -1052,21 +1092,6 @@ public class FeatureTypeSettings extends JalviewDialog colourChanged(false); } - /** - * Converts the slider value to its absolute value by dividing by the - * scaleFactor. Rounding errors are squashed by forcing min/max of slider range - * to the actual min/max of feature score range - * - * @return - */ - private float getRoundedSliderValue() - { - int value = slider.getValue(); - float f = value == slider.getMaximum() ? max - : (value == slider.getMinimum() ? min : value / scaleFactor); - return f; - } - void addActionListener(ActionListener listener) { if (featureSettings != null) @@ -1078,11 +1103,11 @@ public class FeatureTypeSettings extends JalviewDialog } /** - * A helper method to build the drop-down choice of attributes for a feature. If - * 'withRange' is true, then Score, and any attributes with a min-max range, are - * added. If 'withText' is true, Label and any known attributes are added. This - * allows 'categorical numerical' attributes e.g. codon position to be coloured - * by text. + * A helper method to build the drop-down choice of attributes for a feature. + * If 'withRange' is true, then Score, and any attributes with a min-max + * range, are added. If 'withText' is true, Label and any known attributes are + * added. This allows 'categorical numerical' attributes e.g. codon position + * to be coloured by text. *

* Where metadata is available with a description for an attribute, that is * added as a tooltip. @@ -1096,7 +1121,7 @@ public class FeatureTypeSettings extends JalviewDialog * @param withRange * @param withText */ - protected JComboBox populateAttributesDropdown( + protected JComboBox populateAttributesDropdown( List attNames, boolean withRange, boolean withText) { List displayAtts = new ArrayList<>(); @@ -1104,7 +1129,7 @@ public class FeatureTypeSettings extends JalviewDialog if (withText) { - displayAtts.add(MessageManager.getString("label.label")); + displayAtts.add(LABEL_18N); tooltips.add(MessageManager.getString("label.description")); } if (withRange) @@ -1112,8 +1137,8 @@ public class FeatureTypeSettings extends JalviewDialog float[][] minMax = fr.getMinMax().get(featureType); if (minMax != null && minMax[0][0] != minMax[0][1]) { - displayAtts.add(MessageManager.getString("label.score")); - tooltips.add(MessageManager.getString("label.score")); + displayAtts.add(SCORE_18N); + tooltips.add(SCORE_18N); } } @@ -1126,7 +1151,7 @@ public class FeatureTypeSettings extends JalviewDialog { continue; } - displayAtts.add(toAttributeDisplayName(attName)); + displayAtts.add(FeatureMatcher.toAttributeDisplayName(attName)); String desc = fa.getDescription(featureType, attName); if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH) { @@ -1135,8 +1160,10 @@ public class FeatureTypeSettings extends JalviewDialog tooltips.add(desc == null ? "" : desc); } - JComboBox attCombo = JvSwingUtils - .buildComboWithTooltips(displayAtts, tooltips); + // now convert String List to Object List for buildComboWithTooltips + List displayAttsObjects = new ArrayList<>(displayAtts); + JComboBox attCombo = JvSwingUtils + .buildComboWithTooltips(displayAttsObjects, tooltips); return attCombo; } @@ -1158,11 +1185,11 @@ public class FeatureTypeSettings extends JalviewDialog filtersPanel.add(andOrPanel); /* - * panel with filters - populated by refreshFiltersDisplay + * panel with filters - populated by refreshFiltersDisplay, + * which also sets the layout manager */ chooseFiltersPanel = new JPanel(); - LayoutManager box = new BoxLayout(chooseFiltersPanel, BoxLayout.Y_AXIS); - chooseFiltersPanel.setLayout(box); + chooseFiltersPanel.setBackground(Color.white); filtersPanel.add(chooseFiltersPanel); return filtersPanel; @@ -1177,9 +1204,10 @@ public class FeatureTypeSettings extends JalviewDialog { JPanel andOrPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); andOrPanel.setBackground(Color.white); - andOrPanel.setBorder(BorderFactory.createLineBorder(debugBorderColour)); andFilters = new JRadioButton(MessageManager.getString("label.and")); orFilters = new JRadioButton(MessageManager.getString("label.or")); + andFilters.setOpaque(false); + orFilters.setOpaque(false); ActionListener actionListener = new ActionListener() { @Override @@ -1207,7 +1235,7 @@ public class FeatureTypeSettings extends JalviewDialog * for adding a condition. This should be called after a filter has been * removed, added or amended. */ - private void updateFiltersTab() + private void updateFiltersPanel() { /* * clear the panel and list of filter conditions @@ -1224,35 +1252,47 @@ public class FeatureTypeSettings extends JalviewDialog /* * if this feature type has filters set, load them first */ - KeyedMatcherSetI featureFilters = fr.getFeatureFilter(featureType); + FeatureMatcherSetI featureFilters = fr.getFeatureFilter(featureType); if (featureFilters != null) { if (!featureFilters.isAnded()) { orFilters.setSelected(true); } - featureFilters.getMatchers().forEach(matcher -> filters.add(matcher)); + // avoid use of lambda expression to keep SwingJS happy + // featureFilters.getMatchers().forEach(item -> filters.add(item)); + for (FeatureMatcherI matcher : featureFilters.getMatchers()) + { + filters.add(matcher); + } } /* * and an empty filter for the user to populate (add) */ - KeyedMatcherI noFilter = new KeyedMatcher(Condition.values()[0], "", - (String) null); - filters.add(noFilter); + filters.add(FeatureMatcher.NULL_MATCHER); + + /* + * use GridLayout to 'justify' rows to the top of the panel, until + * there are too many to fit in, then fall back on BoxLayout + */ + if (filters.size() <= 5) + { + chooseFiltersPanel.setLayout(new GridLayout(5, 1)); + } + else + { + chooseFiltersPanel.setLayout( + new BoxLayout(chooseFiltersPanel, BoxLayout.Y_AXIS)); + } /* * render the conditions in rows, each in its own JPanel */ int filterIndex = 0; - for (KeyedMatcherI filter : filters) + for (FeatureMatcherI filter : filters) { - String[] attName = filter.getKey(); - Condition condition = filter.getMatcher().getCondition(); - String pattern = filter.getMatcher().getPattern(); - JPanel row = addFilter(attName, attNames, condition, pattern, - filterIndex); - row.setBorder(BorderFactory.createLineBorder(debugBorderColour)); + JPanel row = addFilter(filter, attNames, filterIndex); chooseFiltersPanel.add(row); filterIndex++; } @@ -1264,25 +1304,37 @@ public class FeatureTypeSettings extends JalviewDialog /** * A helper method that constructs a row (panel) with one filter condition: *
    - *
  • a drop-down list of attribute names to choose from
  • + *
  • a drop-down list of Label, Score and attribute names to choose + * from
  • *
  • a drop-down list of conditions to choose from
  • *
  • a text field for input of a match pattern
  • *
  • optionally, a 'remove' button
  • *
- * If attribute, condition or pattern are not null, they are set as defaults for - * the input fields. The 'remove' button is added unless the pattern is null or - * empty (incomplete filter condition). + * The filter values are set as defaults for the input fields. The 'remove' + * button is added unless the pattern is empty (incomplete filter condition). + *

+ * Action handlers on these fields provide for + *

    + *
  • validate pattern field - should be numeric if condition is numeric
  • + *
  • save filters and refresh display on any (valid) change
  • + *
  • remove filter and refresh on 'Remove'
  • + *
  • update conditions list on change of Label/Score/Attribute
  • + *
  • refresh value field tooltip with min-max range on change of + * attribute
  • + *
* - * @param attName + * @param filter * @param attNames - * @param cond - * @param pattern * @param filterIndex * @return */ - protected JPanel addFilter(String[] attName, List attNames, - Condition cond, String pattern, int filterIndex) + protected JPanel addFilter(FeatureMatcherI filter, + List attNames, int filterIndex) { + String[] attName = filter.getAttribute(); + Condition cond = filter.getMatcher().getCondition(); + String pattern = filter.getMatcher().getPattern(); + JPanel filterRow = new JPanel(new FlowLayout(FlowLayout.LEFT)); filterRow.setBackground(Color.white); @@ -1290,10 +1342,14 @@ public class FeatureTypeSettings extends JalviewDialog * drop-down choice of attribute, with description as a tooltip * if we can obtain it */ - final JComboBox attCombo = populateAttributesDropdown(attNames, + final JComboBox attCombo = populateAttributesDropdown(attNames, true, true); + String filterBy = setSelectedAttribute(attCombo, filter); + JComboBox condCombo = new JComboBox<>(); + JTextField patternField = new JTextField(8); + patternField.setText(pattern); /* * action handlers that validate and (if valid) apply changes @@ -1303,11 +1359,10 @@ public class FeatureTypeSettings extends JalviewDialog @Override public void actionPerformed(ActionEvent e) { - if (attCombo.getSelectedItem() != null) + if (validateFilter(patternField, condCombo)) { - if (validateFilter(patternField, condCombo)) + if (updateFilter(attCombo, condCombo, patternField, filterIndex)) { - updateFilter(attCombo, condCombo, patternField, filterIndex); filtersChanged(); } } @@ -1322,13 +1377,14 @@ public class FeatureTypeSettings extends JalviewDialog } }; - if (attName == null) // the 'add a condition' row + if (filter == FeatureMatcher.NULL_MATCHER) // the 'add a condition' row { attCombo.setSelectedIndex(0); } else { - attCombo.setSelectedItem(toAttributeDisplayName(attName)); + attCombo.setSelectedItem( + FeatureMatcher.toAttributeDisplayName(attName)); } attCombo.addItemListener(new ItemListener() { @@ -1340,7 +1396,8 @@ public class FeatureTypeSettings extends JalviewDialog * ensure it is appropriate for the attribute datatype */ populateConditions((String) attCombo.getSelectedItem(), - (Condition) condCombo.getSelectedItem(), condCombo); + (Condition) condCombo.getSelectedItem(), condCombo, + patternField); actionListener.actionPerformed(null); } }); @@ -1350,15 +1407,14 @@ public class FeatureTypeSettings extends JalviewDialog /* * drop-down choice of test condition */ - populateConditions((String) attCombo.getSelectedItem(), cond, - condCombo); + populateConditions(filterBy, cond, condCombo, patternField); + condCombo.setPreferredSize(new Dimension(150, 20)); condCombo.addItemListener(itemListener); filterRow.add(condCombo); /* * pattern to match against */ - patternField.setText(pattern); patternField.addActionListener(actionListener); patternField.addFocusListener(new FocusAdapter() { @@ -1371,14 +1427,30 @@ public class FeatureTypeSettings extends JalviewDialog filterRow.add(patternField); /* + * disable pattern field for condition 'Present / NotPresent' + */ + Condition selectedCondition = (Condition) condCombo.getSelectedItem(); + patternField.setEnabled(selectedCondition.needsAPattern()); + + /* + * if a numeric condition is selected, show the value range + * as a tooltip on the value input field + */ + setNumericHints(filterBy, selectedCondition, patternField); + + /* * add remove button if filter is populated (non-empty pattern) */ - if (pattern != null && pattern.trim().length() > 0) + if (!patternField.isEnabled() + || (pattern != null && pattern.trim().length() > 0)) { - // todo: gif for button drawing '-' or 'x' - JButton removeCondition = new BasicArrowButton(SwingConstants.WEST); - removeCondition - .setToolTipText(MessageManager.getString("label.delete_row")); + JButton removeCondition = new JButton("\u2717"); + // Dingbats cursive x + removeCondition.setBorder(new EmptyBorder(0, 0, 0, 0)); + removeCondition.setBackground(Color.WHITE); + removeCondition.setPreferredSize(new Dimension(23, 17)); + removeCondition.setToolTipText( + MessageManager.getString("label.delete_condition")); removeCondition.addActionListener(new ActionListener() { @Override @@ -1395,25 +1467,101 @@ public class FeatureTypeSettings extends JalviewDialog } /** - * Populates the drop-down list of comparison conditions for the given attribute - * name. The conditions added depend on the datatype of the attribute values. - * The supplied condition is set as the selected item in the list, provided it - * is in the list. + * Sets the selected item in the Label/Score/Attribute drop-down to match the + * filter + * + * @param attCombo + * @param filter + */ + private String setSelectedAttribute(JComboBox attCombo, + FeatureMatcherI filter) + { + String item = null; + if (filter.isByScore()) + { + item = SCORE_18N; + } + else if (filter.isByLabel()) + { + item = LABEL_18N; + } + else + { + item = FeatureMatcher.toAttributeDisplayName(filter.getAttribute()); + } + attCombo.setSelectedItem(item); + return item; + } + + /** + * If a numeric comparison condition is selected, retrieves the min-max range + * for the value (score or attribute), and sets it as a tooltip on the value + * field. If the field is currently empty, then pre-populates it with + *
    + *
  • the minimum value, if condition is > or >=
  • + *
  • the maximum value, if condition is < or <=
  • + *
+ * + * @param attName + * @param selectedCondition + * @param patternField + */ + private void setNumericHints(String attName, Condition selectedCondition, + JTextField patternField) + { + patternField.setToolTipText(""); + + if (selectedCondition.isNumeric()) + { + float[] minMax = getMinMax(attName); + if (minMax != null) + { + String minFormatted = DECFMT_2_2.format(minMax[0]); + String maxFormatted = DECFMT_2_2.format(minMax[1]); + String tip = String.format("(%s - %s)", minFormatted, maxFormatted); + patternField.setToolTipText(tip); + if (patternField.getText().isEmpty()) + { + if (selectedCondition == Condition.GE + || selectedCondition == Condition.GT) + { + patternField.setText(minFormatted); + } + else + { + if (selectedCondition == Condition.LE + || selectedCondition == Condition.LT) + { + patternField.setText(maxFormatted); + } + } + } + } + } + } + + /** + * Populates the drop-down list of comparison conditions for the given + * attribute name. The conditions added depend on the datatype of the + * attribute values. The supplied condition is set as the selected item in the + * list, provided it is in the list. If the pattern is now invalid + * (non-numeric pattern for a numeric condition), it is cleared. * * @param attName * @param cond * @param condCombo + * @param patternField */ - private void populateConditions(String attName, Condition cond, - JComboBox condCombo) + void populateConditions(String attName, Condition cond, + JComboBox condCombo, JTextField patternField) { Datatype type = FeatureAttributes.getInstance().getDatatype(featureType, - fromAttributeDisplayName(attName)); - if (MessageManager.getString("label.label").equals(attName)) + FeatureMatcher.fromAttributeDisplayName(attName)); + if (LABEL_18N.equals(attName)) { type = Datatype.Character; } - else if (MessageManager.getString("label.score").equals(attName)) + else if (SCORE_18N.equals(attName)) { type = Datatype.Number; } @@ -1424,10 +1572,11 @@ public class FeatureTypeSettings extends JalviewDialog ItemListener listener = condCombo.getItemListeners()[0]; condCombo.removeItemListener(listener); boolean condIsValid = false; + condCombo.removeAllItems(); for (Condition c : Condition.values()) { - if ((c.isNumeric() && type != Datatype.Character) + if ((c.isNumeric() && type == Datatype.Number) || (!c.isNumeric() && type != Datatype.Number)) { condCombo.addItem(c); @@ -1450,16 +1599,37 @@ public class FeatureTypeSettings extends JalviewDialog condCombo.setSelectedIndex(0); } + /* + * clear pattern if it is now invalid for condition + */ + if (((Condition) condCombo.getSelectedItem()).isNumeric()) + { + try + { + String pattern = patternField.getText().trim(); + if (pattern.length() > 0) + { + Float.valueOf(pattern); + } + } catch (NumberFormatException e) + { + patternField.setText(""); + } + } + + /* + * restore the listener + */ condCombo.addItemListener(listener); } /** - * Answers true unless a numeric condition has been selected with a non-numeric - * value. Sets the value field to RED with a tooltip if in error. + * Answers true unless a numeric condition has been selected with a + * non-numeric value. Sets the value field to RED with a tooltip if in error. *

- * If the pattern entered is empty, this method returns false, but does not mark - * the field as invalid. This supports selecting an attribute for a new - * condition before a match pattern has been entered. + * If the pattern is expected but is empty, this method returns false, but + * does not mark the field as invalid. This supports selecting an attribute + * for a new condition before a match pattern has been entered. * * @param value * @param condCombo @@ -1473,15 +1643,20 @@ public class FeatureTypeSettings extends JalviewDialog } Condition cond = (Condition) condCombo.getSelectedItem(); + if (!cond.needsAPattern()) + { + return true; + } + value.setBackground(Color.white); value.setToolTipText(""); String v1 = value.getText().trim(); if (v1.length() == 0) { - return false; + // return false; } - if (cond.isNumeric()) + if (cond.isNumeric() && v1.length() > 0) { try { @@ -1500,35 +1675,67 @@ public class FeatureTypeSettings extends JalviewDialog /** * Constructs a filter condition from the given input fields, and replaces the - * condition at filterIndex with the new one + * condition at filterIndex with the new one. Does nothing if the pattern + * field is blank (unless the match condition is one that doesn't require a + * pattern, e.g. 'Is present'). Answers true if the filter was updated, else + * false. + *

+ * This method may update the tooltip on the filter value field to show the + * value range, if a numeric condition is selected. This ensures the tooltip + * is updated when a numeric valued attribute is chosen on the last 'add a + * filter' row. * * @param attCombo * @param condCombo * @param valueField * @param filterIndex */ - protected void updateFilter(JComboBox attCombo, + protected boolean updateFilter(JComboBox attCombo, JComboBox condCombo, JTextField valueField, int filterIndex) { - String attName = (String) attCombo.getSelectedItem(); + String attName; + try + { + attName = (String) attCombo.getSelectedItem(); + } catch (Exception e) + { + Console.error("Problem casting Combo box entry to String"); + attName = attCombo.getSelectedItem().toString(); + } Condition cond = (Condition) condCombo.getSelectedItem(); - String pattern = valueField.getText(); - KeyedMatcherI km = new KeyedMatcher(cond, pattern, - fromAttributeDisplayName(attName)); + String pattern = valueField.getText().trim(); + + setNumericHints(attName, cond, valueField); + + if (pattern.length() == 0 && cond.needsAPattern()) + { + valueField.setEnabled(true); // ensure pattern field is enabled! + return false; + } + + /* + * Construct a matcher that operates on Label, Score, + * or named attribute + */ + FeatureMatcherI km = null; + if (LABEL_18N.equals(attName)) + { + km = FeatureMatcher.byLabel(cond, pattern); + } + else if (SCORE_18N.equals(attName)) + { + km = FeatureMatcher.byScore(cond, pattern); + } + else + { + km = FeatureMatcher.byAttribute(cond, pattern, + FeatureMatcher.fromAttributeDisplayName(attName)); + } filters.set(filterIndex, km); - } - /** - * Makes the dialog visible, at the Feature Colour tab or at the Filters tab - * - * @param coloursTab - */ - public void showTab(boolean coloursTab) - { - setVisible(true); - tabbedPane.setSelectedIndex(coloursTab ? 0 : 1); + return true; } /** @@ -1539,8 +1746,8 @@ public class FeatureTypeSettings extends JalviewDialog *
  • change of match pattern
  • *
  • removal of a condition
  • * - * The inputs are parsed into a combined filter and this is set for the feature - * type, and the alignment redrawn. + * The inputs are parsed into a combined filter and this is set for the + * feature type, and the alignment redrawn. */ protected void filtersChanged() { @@ -1548,12 +1755,13 @@ public class FeatureTypeSettings extends JalviewDialog * update the filter conditions for the feature type */ boolean anded = andFilters.isSelected(); - KeyedMatcherSetI combined = new KeyedMatcherSet(); + FeatureMatcherSetI combined = new FeatureMatcherSet(); - for (KeyedMatcherI filter : filters) + for (FeatureMatcherI filter : filters) { String pattern = filter.getMatcher().getPattern(); - if (pattern.trim().length() > 0) + Condition condition = filter.getMatcher().getCondition(); + if (pattern.trim().length() > 0 || !condition.needsAPattern()) { if (anded) { @@ -1571,8 +1779,26 @@ public class FeatureTypeSettings extends JalviewDialog * (note this might now be an empty filter with no conditions) */ fr.setFeatureFilter(featureType, combined.isEmpty() ? null : combined); - ap.paintAlignment(true, true); + refreshDisplay(true); - updateFiltersTab(); + updateFiltersPanel(); + } + + /** + * Repaints alignment, structure and overview (if shown). If there is a + * complementary view which is showing this view's features, then also + * repaints that. + * + * @param updateStructsAndOverview + */ + void refreshDisplay(boolean updateStructsAndOverview) + { + ap.paintAlignment(true, updateStructsAndOverview); + AlignViewportI complement = ap.getAlignViewport().getCodingComplement(); + if (complement != null && complement.isShowComplementFeatures()) + { + AlignFrame af2 = Desktop.getAlignFrameFor(complement); + af2.alignPanel.paintAlignment(true, updateStructsAndOverview); + } } }