JAL-2808 JAL-3032 drop tabbed layout for Colours and Filters
[jalview.git] / src / jalview / gui / FeatureTypeSettings.java
index 372234a..01ef48c 100644 (file)
@@ -25,20 +25,20 @@ import jalview.api.FeatureColourI;
 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.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 +47,7 @@ import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
+import java.text.DecimalFormat;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -78,6 +79,12 @@ import javax.swing.plaf.basic.BasicArrowButton;
  */
 public class FeatureTypeSettings extends JalviewDialog
 {
+  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,6 +101,9 @@ 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
    */
@@ -111,7 +121,7 @@ public class FeatureTypeSettings extends JalviewDialog
    */
   private final FeatureColourI originalColour;
 
-  private final KeyedMatcherSetI originalFilter;
+  private final FeatureMatcherSetI originalFilter;
 
   /*
    * set flag to true when setting values programmatically,
@@ -119,10 +129,20 @@ public class FeatureTypeSettings extends JalviewDialog
    */
   private 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;
 
+  /*
+   * scale factor for conversion between absolute min-max and slider
+   */
   private float scaleFactor;
 
   /*
@@ -177,15 +197,13 @@ public class FeatureTypeSettings extends JalviewDialog
   /*
    * filters for the currently selected feature type
    */
-  private List<KeyedMatcherI> filters;
-
-  // set white normally, black to debug layout
-  private Color debugBorderColour = Color.white;
+  private List<FeatureMatcherI> filters;
 
   private JPanel chooseFiltersPanel;
 
-  private JTabbedPane tabbedPane;
-
+  // for alternative 2 tabs layout (Colour, Filters)
+//  private JTabbedPane tabbedPane;
+  
   /**
    * Constructor
    * 
@@ -236,7 +254,7 @@ 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, blocking, title, 500, 500);
 
     waitForInput();
   }
@@ -275,13 +293,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 +323,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 +335,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)
@@ -384,8 +401,8 @@ public class FeatureTypeSettings extends JalviewDialog
   private void initialise()
   {
     this.setLayout(new BorderLayout());
-    tabbedPane = new JTabbedPane();
-    this.add(tabbedPane, BorderLayout.CENTER);
+//    tabbedPane = new JTabbedPane();
+//    this.add(tabbedPane, BorderLayout.CENTER);
 
     /*
      * an ActionListener that applies colour changes
@@ -400,18 +417,20 @@ public class FeatureTypeSettings extends JalviewDialog
     };
 
     /*
-     * first tab: colour options
+     * first panel/tab: colour options
      */
     JPanel coloursPanel = initialiseColoursPanel();
-    tabbedPane.addTab(MessageManager.getString("action.colour"),
-            coloursPanel);
+//    tabbedPane.addTab(MessageManager.getString("action.colour"),
+//            coloursPanel);
+    this.add(coloursPanel, BorderLayout.NORTH);
 
     /*
-     * second tab: filter options
+     * second panel/tab: filter options
      */
     JPanel filtersPanel = initialiseFiltersPanel();
-    tabbedPane.addTab(MessageManager.getString("label.filters"),
-            filtersPanel);
+//    tabbedPane.addTab(MessageManager.getString("label.filters"),
+//            filtersPanel);
+    this.add(filtersPanel, BorderLayout.CENTER);
 
     JPanel okCancelPanel = initialiseOkCancelPanel();
 
@@ -422,32 +441,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:
+   * <ul>
+   * <li>of feature score, if colour or filter is by Score</li>
+   * <li>else of the selected attribute</li>
+   * </ul>
+   * 
+   * @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;
   }
 
   /**
@@ -702,15 +736,18 @@ 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);
+//    JvSwingUtils.createTitledBorder(simpleColourPanel,
+//            MessageManager.getString("label.simple"), true);
     colourByPanel.add(simpleColourPanel);
 
     simpleColour = new JRadioButton(
@@ -835,7 +872,7 @@ public class FeatureTypeSettings extends JalviewDialog
      * ensure min-max range is for the latest choice of 
      * 'graduated colour by'
      */
-    updateMinMax();
+    updateColourMinMax();
 
     FeatureColourI acg = makeColourFromInputs();
 
@@ -872,9 +909,10 @@ public class FeatureTypeSettings extends JalviewDialog
       FeatureColourI fc = new FeatureColour(c, c, null, 0f, 0f);
       fc.setColourByLabel(true);
       String byWhat = (String) colourByTextCombo.getSelectedItem();
-      if (!MessageManager.getString("label.label").equals(byWhat))
+      if (!LABEL_18N.equals(byWhat))
       {
-        fc.setAttributeName(fromAttributeDisplayName(byWhat));
+        fc.setAttributeName(
+                FeatureMatcher.fromAttributeDisplayName(byWhat));
       }
       return fc;
     }
@@ -929,9 +967,9 @@ public class FeatureTypeSettings extends JalviewDialog
      * 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 +994,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()
   {
@@ -1104,7 +1118,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 +1126,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 +1140,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)
       {
@@ -1158,11 +1172,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,7 +1191,6 @@ 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"));
     ActionListener actionListener = new ActionListener()
@@ -1224,7 +1237,7 @@ 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())
@@ -1237,22 +1250,29 @@ public class FeatureTypeSettings extends JalviewDialog
     /*
      * 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 +1284,36 @@ public class FeatureTypeSettings extends JalviewDialog
   /**
    * A helper method that constructs a row (panel) with one filter condition:
    * <ul>
-   * <li>a drop-down list of attribute names to choose from</li>
+   * <li>a drop-down list of Label, Score and attribute names to choose from</li>
    * <li>a drop-down list of conditions to choose from</li>
    * <li>a text field for input of a match pattern</li>
    * <li>optionally, a 'remove' button</li>
    * </ul>
-   * 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).
+   * <p>
+   * Action handlers on these fields provide for
+   * <ul>
+   * <li>validate pattern field - should be numeric if condition is numeric</li>
+   * <li>save filters and refresh display on any (valid) change</li>
+   * <li>remove filter and refresh on 'Remove'</li>
+   * <li>update conditions list on change of Label/Score/Attribute</li>
+   * <li>refresh value field tooltip with min-max range on change of
+   * attribute</li>
+   * </ul>
    * 
-   * @param attName
+   * @param filter
    * @param attNames
-   * @param cond
-   * @param pattern
    * @param filterIndex
    * @return
    */
-  protected JPanel addFilter(String[] attName, List<String[]> attNames,
-          Condition cond, String pattern, int filterIndex)
+  protected JPanel addFilter(FeatureMatcherI filter,
+          List<String[]> 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);
 
@@ -1292,8 +1323,12 @@ public class FeatureTypeSettings extends JalviewDialog
      */
     final JComboBox<String> attCombo = populateAttributesDropdown(attNames,
             true, true);
+    String filterBy = setSelectedAttribute(attCombo, filter);
+
     JComboBox<Condition> condCombo = new JComboBox<>();
+
     JTextField patternField = new JTextField(8);
+    patternField.setText(pattern);
 
     /*
      * action handlers that validate and (if valid) apply changes
@@ -1303,11 +1338,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 +1356,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 +1375,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 +1386,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,9 +1406,22 @@ 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);
@@ -1395,25 +1443,101 @@ public class FeatureTypeSettings extends JalviewDialog
   }
 
   /**
+   * Sets the selected item in the Label/Score/Attribute drop-down to match the
+   * filter
+   * 
+   * @param attCombo
+   * @param filter
+   */
+  private String setSelectedAttribute(JComboBox<String> 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
+   * <ul>
+   * <li>the minimum value, if condition is > or >=</li>
+   * <li>the maximum value, if condition is < or <=</li>
+   * </ul>
+   * 
+   * @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.
+   * 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<Condition> condCombo)
+          JComboBox<Condition> 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 +1548,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,6 +1575,27 @@ 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);
   }
 
@@ -1457,8 +1603,8 @@ public class FeatureTypeSettings extends JalviewDialog
    * 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.
    * <p>
-   * 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
+   * 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
@@ -1473,15 +1619,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,24 +1651,58 @@ 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.
+   * <p>
+   * 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<String> attCombo,
+  protected boolean updateFilter(JComboBox<String> attCombo,
           JComboBox<Condition> condCombo, JTextField valueField,
           int filterIndex)
   {
     String attName = (String) attCombo.getSelectedItem();
     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);
+
+    return true;
   }
 
   /**
@@ -1528,7 +1713,7 @@ public class FeatureTypeSettings extends JalviewDialog
   public void showTab(boolean coloursTab)
   {
     setVisible(true);
-    tabbedPane.setSelectedIndex(coloursTab ? 0 : 1);
+//   tabbedPane.setSelectedIndex(coloursTab ? 0 : 1);
   }
 
   /**
@@ -1548,12 +1733,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)
         {