Merge branch 'bug/JAL-2983negativeSliderMin' into develop
[jalview.git] / src / jalview / gui / FeatureTypeSettings.java
index 73a22fa..54eeba7 100644 (file)
@@ -20,6 +20,7 @@
  */
 package jalview.gui;
 
+import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureColourI;
 import jalview.bin.Cache;
@@ -30,6 +31,7 @@ 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;
@@ -48,6 +50,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;
@@ -57,12 +61,10 @@ 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.JTextField;
 import javax.swing.border.EmptyBorder;
 import javax.swing.border.LineBorder;
@@ -78,6 +80,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");
 
@@ -112,9 +116,9 @@ public class FeatureTypeSettings extends JalviewDialog
   /*
    * 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
@@ -127,7 +131,7 @@ public class FeatureTypeSettings extends JalviewDialog
    * 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
@@ -141,45 +145,30 @@ 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
    */
-  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();
 
-  /**
-   * colours and filters are shown in tabbed view or single content pane
-   */
-  JPanel coloursPanel, filtersPanel;
+  JPanel coloursPanel;
+  
+  JPanel filtersPanel;
 
   JPanel singleColour = new JPanel();
 
-  private JPanel minColour = new JPanel();
+  JPanel minColour = new JPanel();
 
-  private JPanel maxColour = new JPanel();
+  JPanel maxColour = new JPanel();
 
   private JComboBox<Object> threshold = new JComboBox<>();
 
-  private JSlider slider = new JSlider();
+  private Slider slider;
 
-  private JTextField thresholdValue = new JTextField(20);
+  JTextField thresholdValue = new JTextField(20);
 
   private JCheckBox thresholdIsMin = new JCheckBox();
 
@@ -211,7 +200,7 @@ public class FeatureTypeSettings extends JalviewDialog
   /*
    * filters for the currently selected feature type
    */
-  private List<FeatureMatcherI> filters;
+  List<FeatureMatcherI> filters;
 
   private JPanel chooseFiltersPanel;
 
@@ -240,9 +229,9 @@ public class FeatureTypeSettings extends JalviewDialog
       return;
     }
     
-    updateColoursTab();
+    updateColoursPanel();
     
-    updateFiltersTab();
+    updateFiltersPanel();
     
     adjusting = false;
     
@@ -256,10 +245,10 @@ public class FeatureTypeSettings extends JalviewDialog
   }
 
   /**
-   * Configures the widgets on the Colours tab according to the current feature
+   * 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);
 
@@ -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);
       }
@@ -415,13 +399,13 @@ public class FeatureTypeSettings extends JalviewDialog
     };
 
     /*
-     * first panel/tab: colour options
+     * first panel: colour options
      */
     JPanel coloursPanel = initialiseColoursPanel();
     this.add(coloursPanel, BorderLayout.NORTH);
 
     /*
-     * second panel/tab: filter options
+     * second panel: filter options
      */
     JPanel filtersPanel = initialiseFiltersPanel();
     this.add(filtersPanel, BorderLayout.CENTER);
@@ -501,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
@@ -552,7 +537,8 @@ 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);
         }
       }
     });
@@ -568,7 +554,8 @@ 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);
         }
       }
     });
@@ -653,6 +640,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 +657,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();
         }
@@ -687,7 +674,7 @@ public class FeatureTypeSettings extends JalviewDialog
          */
         if (ap != null)
         {
-          ap.paintAlignment(true, true);
+          refreshDisplay(true);
         }
       }
     });
@@ -752,6 +739,7 @@ public class FeatureTypeSettings extends JalviewDialog
     simpleColour = new JRadioButton(
             MessageManager.getString("label.simple_colour"));
     simpleColour.setPreferredSize(new Dimension(RADIO_WIDTH, 20));
+    simpleColour.setOpaque(false);
     simpleColour.addItemListener(new ItemListener()
     {
       @Override
@@ -784,7 +772,8 @@ 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);
         }
       }
     });
@@ -802,6 +791,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
@@ -846,17 +836,29 @@ 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);
   }
 
   /**
@@ -888,9 +890,9 @@ public class FeatureTypeSettings extends JalviewDialog
      * save the colour, and repaint stuff
      */
     fr.setColour(featureType, acg);
-    ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview);
+    refreshDisplay(updateStructsAndOverview);
 
-    updateColoursTab();
+    updateColoursPanel();
   }
 
   /**
@@ -915,7 +917,7 @@ public class FeatureTypeSettings extends JalviewDialog
     }
     float minValue = min;
     float maxValue = max;
-    final int thresholdOption = threshold.getSelectedIndex();
+    int thresholdOption = threshold.getSelectedIndex();
     if (thresholdIsMin.isSelected()
             && thresholdOption == ABOVE_THRESHOLD_OPTION)
     {
@@ -1027,7 +1029,7 @@ public class FeatureTypeSettings extends JalviewDialog
   {
     fr.setColour(featureType, originalColour);
     fr.setFeatureFilter(featureType, originalFilter);
-    ap.paintAlignment(true, true);
+    refreshDisplay(true);
   }
 
   /**
@@ -1045,8 +1047,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 +1061,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,
@@ -1188,6 +1202,8 @@ public class FeatureTypeSettings extends JalviewDialog
     andOrPanel.setBackground(Color.white);
     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
@@ -1215,7 +1231,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
@@ -1239,7 +1255,12 @@ public class FeatureTypeSettings extends JalviewDialog
       {
         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);
+      }
     }
 
     /*
@@ -1419,10 +1440,13 @@ public class FeatureTypeSettings extends JalviewDialog
     if (!patternField.isEnabled()
             || (pattern != null && pattern.trim().length() > 0))
     {
-      JButton removeCondition = new JButton("\u2717"); // Dingbats cursive x
+      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.setBorder(new EmptyBorder(0, 0, 0, 0));
       removeCondition.addActionListener(new ActionListener()
       {
         @Override
@@ -1524,7 +1548,7 @@ public class FeatureTypeSettings extends JalviewDialog
    * @param condCombo
    * @param patternField
    */
-  private void populateConditions(String attName, Condition cond,
+  void populateConditions(String attName, Condition cond,
           JComboBox<Condition> condCombo, JTextField patternField)
   {
     Datatype type = FeatureAttributes.getInstance().getDatatype(featureType,
@@ -1751,45 +1775,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();
   }
 
   /**
-   * Answers the slider value, converted to the corresponding 'true' value by
-   * applying scaling
+   * Repaints alignment, structure and overview (if shown). If there is a
+   * complementary view which is showing this view's features, then also
+   * repaints that.
    * 
-   * @return
+   * @param updateStructsAndOverview
    */
-  float getSliderValue()
+  void refreshDisplay(boolean updateStructsAndOverview)
   {
-    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())
+    ap.paintAlignment(true, updateStructsAndOverview);
+    AlignViewportI complement = ap.getAlignViewport().getCodingComplement();
+    if (complement != null && complement.isShowComplementFeatures())
     {
-      f = min;
+      AlignFrame af2 = Desktop.getAlignFrameFor(complement);
+      af2.alignPanel.paintAlignment(true, updateStructsAndOverview);
     }
-    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);
   }
 }