From b645f92e338a4eea33bd2813c27917fb0fe5cc4d Mon Sep 17 00:00:00 2001 From: gmungoc Date: Thu, 18 Jul 2019 14:24:21 +0100 Subject: [PATCH] JAL-3371 JAL-2983 add gui.Slider to handle negative, float or % values --- src/jalview/gui/AnnotationColourChooser.java | 33 +++----- src/jalview/gui/AnnotationColumnChooser.java | 28 +++---- src/jalview/gui/AnnotationRowFilter.java | 91 +++++++++++++++++++-- src/jalview/gui/FeatureSettings.java | 5 +- src/jalview/gui/FeatureTypeSettings.java | 95 ++++++---------------- src/jalview/gui/Slider.java | 113 ++++++++++++++++++++++++++ 6 files changed, 251 insertions(+), 114 deletions(-) create mode 100644 src/jalview/gui/Slider.java diff --git a/src/jalview/gui/AnnotationColourChooser.java b/src/jalview/gui/AnnotationColourChooser.java index 60ad75d..7ed178b 100644 --- a/src/jalview/gui/AnnotationColourChooser.java +++ b/src/jalview/gui/AnnotationColourChooser.java @@ -53,8 +53,6 @@ import net.miginfocom.swing.MigLayout; @SuppressWarnings("serial") public class AnnotationColourChooser extends AnnotationRowFilter { - private static final int ONETHOUSAND = 1000; - private ColourSchemeI oldcs; private JButton defColours; @@ -147,7 +145,8 @@ public class AnnotationColourChooser extends AnnotationRowFilter "error.implementation_error_dont_know_about_threshold_setting")); } thresholdIsMin.setSelected(acg.isThresholdIsMinMax()); - thresholdValue.setText("" + acg.getAnnotationThreshold()); + thresholdValue + .setText(String.valueOf(acg.getAnnotationThreshold())); } jbInit(); @@ -338,7 +337,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter { updateView(); } - getCurrentAnnotation().threshold.value = slider.getValue() / 1000f; + getCurrentAnnotation().threshold.value = getSliderValue(); propagateSeqAssociatedThreshold(updateAllAnnotation, getCurrentAnnotation()); ap.paintAlignment(false, false); @@ -378,6 +377,7 @@ public class AnnotationColourChooser extends AnnotationRowFilter thresholdValue.setEnabled(true); thresholdIsMin.setEnabled(!useOriginalColours.isSelected()); + final AlignmentAnnotation currentAnnotation = getCurrentAnnotation(); if (selectedThresholdItem == AnnotationColourGradient.NO_THRESHOLD) { slider.setEnabled(false); @@ -386,33 +386,26 @@ public class AnnotationColourChooser extends AnnotationRowFilter thresholdIsMin.setEnabled(false); } else if (selectedThresholdItem != AnnotationColourGradient.NO_THRESHOLD - && getCurrentAnnotation().threshold == null) + && currentAnnotation.threshold == null) { - getCurrentAnnotation().setThreshold(new GraphLine( - (getCurrentAnnotation().graphMax - - getCurrentAnnotation().graphMin) / 2f, + currentAnnotation.setThreshold(new GraphLine( + (currentAnnotation.graphMax - currentAnnotation.graphMin) + / 2f, "Threshold", Color.black)); } if (selectedThresholdItem != AnnotationColourGradient.NO_THRESHOLD) { adjusting = true; - float range = getCurrentAnnotation().graphMax * ONETHOUSAND - - getCurrentAnnotation().graphMin * ONETHOUSAND; - - slider.setMinimum( - (int) (getCurrentAnnotation().graphMin * ONETHOUSAND)); - slider.setMaximum( - (int) (getCurrentAnnotation().graphMax * ONETHOUSAND)); - slider.setValue( - (int) (getCurrentAnnotation().threshold.value * ONETHOUSAND)); - thresholdValue.setText(getCurrentAnnotation().threshold.value + ""); - slider.setMajorTickSpacing((int) (range / 10f)); + setSliderModel(currentAnnotation.graphMin, currentAnnotation.graphMax, + currentAnnotation.threshold.value); slider.setEnabled(true); + + setThresholdValueText(); thresholdValue.setEnabled(true); adjusting = false; } - colorAlignmentContaining(getCurrentAnnotation(), selectedThresholdItem); + colorAlignmentContaining(currentAnnotation, selectedThresholdItem); ap.alignmentChanged(); } diff --git a/src/jalview/gui/AnnotationColumnChooser.java b/src/jalview/gui/AnnotationColumnChooser.java index 5adecad..82ec0e7 100644 --- a/src/jalview/gui/AnnotationColumnChooser.java +++ b/src/jalview/gui/AnnotationColumnChooser.java @@ -21,6 +21,7 @@ package jalview.gui; +import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.HiddenColumns; import jalview.io.cache.JvCacheableInputBox; import jalview.schemes.AnnotationColourGradient; @@ -254,7 +255,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter { if (slider.isEnabled()) { - getCurrentAnnotation().threshold.value = slider.getValue() / 1000f; + getCurrentAnnotation().threshold.value = getSliderValue(); updateView(); propagateSeqAssociatedThreshold(updateAllAnnotation, getCurrentAnnotation()); @@ -284,6 +285,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter thresholdValue.setEnabled(true); percentThreshold.setEnabled(true); + final AlignmentAnnotation currentAnnotation = getCurrentAnnotation(); if (selectedThresholdItem == AnnotationColourGradient.NO_THRESHOLD) { slider.setEnabled(false); @@ -294,26 +296,22 @@ public class AnnotationColumnChooser extends AnnotationRowFilter } else if (selectedThresholdItem != AnnotationColourGradient.NO_THRESHOLD) { - if (getCurrentAnnotation().threshold == null) + if (currentAnnotation.threshold == null) { - getCurrentAnnotation().setThreshold(new jalview.datamodel.GraphLine( - (getCurrentAnnotation().graphMax - - getCurrentAnnotation().graphMin) / 2f, + currentAnnotation.setThreshold(new jalview.datamodel.GraphLine( + (currentAnnotation.graphMax + - currentAnnotation.graphMin) / 2f, "Threshold", Color.black)); } adjusting = true; - float range = getCurrentAnnotation().graphMax * 1000 - - getCurrentAnnotation().graphMin * 1000; - slider.setMinimum((int) (getCurrentAnnotation().graphMin * 1000)); - slider.setMaximum((int) (getCurrentAnnotation().graphMax * 1000)); - slider.setValue( - (int) (getCurrentAnnotation().threshold.value * 1000)); + setSliderModel(currentAnnotation.graphMin, + currentAnnotation.graphMax, + currentAnnotation.threshold.value); setThresholdValueText(); - slider.setMajorTickSpacing((int) (range / 10f)); slider.setEnabled(true); thresholdValue.setEnabled(true); adjusting = false; @@ -321,10 +319,10 @@ public class AnnotationColumnChooser extends AnnotationRowFilter // build filter params filterParams.setThresholdType( AnnotationFilterParameter.ThresholdType.NO_THRESHOLD); - if (getCurrentAnnotation().isQuantitative()) + if (currentAnnotation.isQuantitative()) { filterParams - .setThresholdValue(getCurrentAnnotation().threshold.value); + .setThresholdValue(currentAnnotation.threshold.value); if (selectedThresholdItem == AnnotationColourGradient.ABOVE_THRESHOLD) { @@ -380,7 +378,7 @@ public class AnnotationColumnChooser extends AnnotationRowFilter // adding them to the selection av.showAllHiddenColumns(); av.getColumnSelection().filterAnnotations( - getCurrentAnnotation().annotations, filterParams); + currentAnnotation.annotations, filterParams); boolean hideCols = getActionOption() == ACTION_OPTION_HIDE; if (hideCols) diff --git a/src/jalview/gui/AnnotationRowFilter.java b/src/jalview/gui/AnnotationRowFilter.java index f13cb10..6f72e10 100644 --- a/src/jalview/gui/AnnotationRowFilter.java +++ b/src/jalview/gui/AnnotationRowFilter.java @@ -35,6 +35,8 @@ 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.util.HashMap; import java.util.Map; import java.util.Vector; @@ -44,7 +46,6 @@ import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JInternalFrame; import javax.swing.JPanel; -import javax.swing.JSlider; import javax.swing.JTextField; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; @@ -52,6 +53,10 @@ import javax.swing.event.ChangeListener; @SuppressWarnings("serial") public abstract class AnnotationRowFilter extends JPanel { + private static final String TWO_DP = "%.2f"; + + private final static MathContext FOUR_SIG_FIG = new MathContext(4); + protected AlignViewport av; protected AlignmentPanel ap; @@ -64,7 +69,7 @@ public abstract class AnnotationRowFilter extends JPanel protected JCheckBox percentThreshold = new JCheckBox(); - protected JSlider slider = new JSlider(); + protected Slider slider; protected JTextField thresholdValue = new JTextField(20); @@ -103,6 +108,8 @@ public abstract class AnnotationRowFilter extends JPanel { this.av = viewport; this.ap = alignPanel; + this.slider = new Slider(0f, 100f, 50f); + thresholdValue.addFocusListener(new FocusAdapter() { @Override @@ -140,16 +147,62 @@ public abstract class AnnotationRowFilter extends JPanel adjusting = true; if (percentThreshold.isSelected()) { - thresholdValue.setText("" + (slider.getValue() - slider.getMinimum()) - * 100f / (slider.getMaximum() - slider.getMinimum())); + thresholdValue + .setText(String.format(TWO_DP, getSliderPercentageValue())); } else { - thresholdValue.setText((slider.getValue() / 1000f) + ""); + /* + * round to 4 significant digits without trailing zeroes + */ + float f = getSliderValue(); + BigDecimal formatted = new BigDecimal(f).round(FOUR_SIG_FIG) + .stripTrailingZeros(); + thresholdValue.setText(formatted.toPlainString()); } adjusting = oldadj; } + /** + * Answers the value of the slider position (descaled to 'true' value) + * + * @return + */ + protected float getSliderValue() + { + return slider.getSliderValue(); + } + + /** + * Sets the slider value (scaled from the true value to the slider range) + * + * @param value + */ + protected void setSliderValue(float value) + { + slider.setSliderValue(value); + } + /** + * Answers the value of the slider position as a percentage between minimum and + * maximum of its range + * + * @return + */ + protected float getSliderPercentageValue() + { + return slider.getSliderPercentageValue(); + } + + /** + * Sets the slider position for a given percentage value of its min-max range + * + * @param pct + */ + protected void setSliderPercentageValue(float pct) + { + slider.setSliderPercentageValue(pct); + } + protected void addSliderMouseListeners() { @@ -290,6 +343,10 @@ public abstract class AnnotationRowFilter extends JPanel updateView(); } + /** + * Updates the slider position, and the display, for an update in the slider's + * text input field + */ protected void thresholdValue_actionPerformed() { try @@ -297,12 +354,11 @@ public abstract class AnnotationRowFilter extends JPanel float f = Float.parseFloat(thresholdValue.getText()); if (percentThreshold.isSelected()) { - slider.setValue(slider.getMinimum() + ((int) ((f / 100f) - * (slider.getMaximum() - slider.getMinimum())))); + setSliderPercentageValue(f); } else { - slider.setValue((int) (f * 1000)); + setSliderValue(f); } updateView(); } catch (NumberFormatException ex) @@ -528,4 +584,23 @@ public abstract class AnnotationRowFilter extends JPanel valueChanged(true); } } + + /** + * Sets the min-max range and current value of the slider, with rescaling from + * true values to slider range as required + * + * @param min + * @param max + * @param value + */ + protected void setSliderModel(float min, float max, float value) + { + slider.setSliderModel(min, max, value); + + /* + * tick mark every 10th position + */ + slider.setMajorTickSpacing( + (slider.getMaximum() - slider.getMinimum()) / 10); + } } diff --git a/src/jalview/gui/FeatureSettings.java b/src/jalview/gui/FeatureSettings.java index 9ca409b..3a04b88 100644 --- a/src/jalview/gui/FeatureSettings.java +++ b/src/jalview/gui/FeatureSettings.java @@ -153,7 +153,7 @@ public class FeatureSettings extends JPanel JPanel groupPanel; - JSlider transparency = new JSlider(); + JSlider transparency; /* * when true, constructor is still executing - so ignore UI events @@ -187,7 +187,7 @@ public class FeatureSettings extends JPanel // save transparency for restore on Cancel originalTransparency = fr.getTransparency(); int originalTransparencyAsPercent = (int) (originalTransparency * 100); - transparency.setMaximum(100 - originalTransparencyAsPercent); + transparency = new JSlider(0, 70, 100 - originalTransparencyAsPercent); originalFilters = new HashMap<>(fr.getFeatureFilters()); // shallow copy @@ -1260,7 +1260,6 @@ public class FeatureSettings extends JPanel } }); - transparency.setMaximum(70); transparency.setToolTipText( MessageManager.getString("label.transparency_tip")); diff --git a/src/jalview/gui/FeatureTypeSettings.java b/src/jalview/gui/FeatureTypeSettings.java index 73a22fa..92918d4 100644 --- a/src/jalview/gui/FeatureTypeSettings.java +++ b/src/jalview/gui/FeatureTypeSettings.java @@ -48,6 +48,8 @@ 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; @@ -62,7 +64,6 @@ import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; -import javax.swing.JSlider; import javax.swing.JTextField; import javax.swing.border.EmptyBorder; import javax.swing.border.LineBorder; @@ -78,6 +79,8 @@ import javax.swing.event.ChangeListener; */ 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"); @@ -141,20 +144,6 @@ public class FeatureTypeSettings extends JalviewDialog private float max; /* - * scale factor for conversion between absolute min-max and slider - * slider value is true value * scale factor - */ - private float sliderScaleFactor; - - /* - * value to subtract from slider value before conversion; - * normally zero, but >0 if min is negative - * true value = (slider value - offset) / scale factor - * slider value = true value * scale factor + offset - */ - private int sliderOffset; - - /* * radio button group, to select what to colour by: * simple colour, by category (text), or graduated */ @@ -177,7 +166,7 @@ public class FeatureTypeSettings extends JalviewDialog private JComboBox threshold = new JComboBox<>(); - private JSlider slider = new JSlider(); + private Slider slider; private JTextField thresholdValue = new JTextField(20); @@ -358,14 +347,9 @@ public class FeatureTypeSettings extends JalviewDialog * valid options are offered in the combo box) * offset slider to have only non-negative values if necessary (JAL-2983) */ - sliderScaleFactor = (max == min) ? 1f : 100f / (max - min); - float range = (max - min) * sliderScaleFactor; - int minimum = (int) (min * sliderScaleFactor); - int maximum = (int) (max * sliderScaleFactor); - sliderOffset = minimum < 0f ? -minimum : 0; - slider.setMinimum(minimum + sliderOffset); - slider.setMaximum(maximum + sliderOffset); - 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); @@ -377,8 +361,8 @@ public class FeatureTypeSettings extends JalviewDialog fc.isAboveThreshold() ? ABOVE_THRESHOLD_OPTION : BELOW_THRESHOLD_OPTION); slider.setEnabled(true); - setSliderValue(fc.getThreshold()); - thresholdValue.setText(String.valueOf(fc.getThreshold())); + slider.setSliderValue(fc.getThreshold()); + setThresholdValueText(fc.getThreshold()); thresholdValue.setEnabled(true); thresholdIsMin.setEnabled(true); } @@ -653,6 +637,7 @@ public class FeatureTypeSettings extends JalviewDialog thresholdValue_actionPerformed(); } }); + slider = new Slider(0f, 100f, 50f); slider.setPaintLabels(false); slider.setPaintTicks(true); slider.setBackground(Color.white); @@ -669,8 +654,7 @@ public class FeatureTypeSettings extends JalviewDialog { if (!adjusting) { - thresholdValue - .setText(String.format("%.3f", getSliderValue())); + setThresholdValueText(slider.getSliderValue()); thresholdValue.setBackground(Color.white); // to reset red for invalid sliderValueChanged(); } @@ -1045,8 +1029,8 @@ public class FeatureTypeSettings extends JalviewDialog float f = Float.parseFloat(thresholdValue.getText()); f = Float.max(f, this.min); f = Float.min(f, this.max); - thresholdValue.setText(String.valueOf(f)); - setSliderValue(f * sliderScaleFactor); + setThresholdValueText(f); + slider.setSliderValue(f); threshline.value = f; thresholdValue.setBackground(Color.white); // ok adjusting = false; @@ -1059,13 +1043,25 @@ public class FeatureTypeSettings extends JalviewDialog } /** + * 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 = getSliderValue(); + threshline.value = slider.getSliderValue(); /* * repaint alignment, but not Overview or structure, @@ -1755,41 +1751,4 @@ public class FeatureTypeSettings extends JalviewDialog updateFiltersTab(); } - - /** - * Answers the slider value, converted to the corresponding 'true' value by - * applying scaling - * - * @return - */ - float getSliderValue() - { - int value = slider.getValue(); - float f = (value - sliderOffset) / sliderScaleFactor; - - /* - * avoid rounding errors at min/max of range - */ - if (value == slider.getMaximum()) - { - f = max; - } - else if (value == slider.getMinimum()) - { - f = min; - } - return f; - } - - /** - * Sets the slider value, converted from the corresponding 'true' value by - * applying scaling - * - * @param f - */ - void setSliderValue(float f) - { - float v = f * sliderScaleFactor + sliderOffset; - slider.setValue((int) v); - } } diff --git a/src/jalview/gui/Slider.java b/src/jalview/gui/Slider.java new file mode 100644 index 0000000..b913ba0 --- /dev/null +++ b/src/jalview/gui/Slider.java @@ -0,0 +1,113 @@ +package jalview.gui; + +import javax.swing.JSlider; + +/** + * A modified {@code javax.swing.JSlider} that + * + * + * @author gmcarstairs + */ +@SuppressWarnings("serial") +public class Slider extends JSlider +{ + /* + * 'true' value corresponding to zero on the slider + */ + private float trueMin; + + /* + * 'true' value corresponding to slider maximum + */ + private float trueMax; + + /* + * scaleFactor applied to true value range to give a + * slider range of 0 - 100 + */ + private int sliderScaleFactor; + + /** + * Constructor that rescales min - max to 0 - 100 for the slider + * + * @param min + * @param max + * @param value + */ + public Slider(float min, float max, float value) + { + super(); + setSliderModel(min, max, value); + } + + /** + * Sets the min-max range and current value of the slider, with rescaling from + * true values to slider range as required + * + * @param min + * @param max + * @param value + */ + public void setSliderModel(float min, float max, float value) + { + trueMin = min; + trueMax = max; + setMinimum(0); + sliderScaleFactor = (int) (100f / (max - min)); + int sliderMax = (int) ((max - min) * sliderScaleFactor); + setMaximum(sliderMax); + setSliderValue(value); + } + + /** + * Answers the value of the slider position (descaled to 'true' value) + * + * @return + */ + public float getSliderValue() + { + /* + * convert slider max to 'true max' in case of rounding errors + */ + int value = getValue(); + return value == getMaximum() ? trueMax + : value / (float) sliderScaleFactor + trueMin; + } + + /** + * Sets the slider value (scaled from the true value to the slider range) + * + * @param value + */ + public void setSliderValue(float value) + { + setValue((int) ((value - trueMin) * sliderScaleFactor)); + } + + /** + * Answers the value of the slider position as a percentage between minimum and + * maximum of its range + * + * @return + */ + public float getSliderPercentageValue() + { + return (getValue() - getMinimum()) * 100f + / (getMaximum() - getMinimum()); + } + + /** + * Sets the slider position for a given percentage value of its min-max range + * + * @param pct + */ + public void setSliderPercentageValue(float pct) + { + float pc = pct / 100f * getMaximum(); + setValue((int) pc); + } +} -- 1.7.10.2