JAL-2808 add attribute filter options to graduate colour dialog
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 30 Oct 2017 16:57:24 +0000 (16:57 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 30 Oct 2017 16:57:24 +0000 (16:57 +0000)
src/jalview/gui/FeatureColourChooser.java

index d8db546..937b48e 100644 (file)
@@ -22,19 +22,27 @@ package jalview.gui;
 
 import jalview.api.FeatureColourI;
 import jalview.datamodel.GraphLine;
+import jalview.datamodel.features.FeatureAttributes;
 import jalview.schemes.FeatureColour;
 import jalview.util.MessageManager;
+import jalview.util.matcher.Condition;
+import jalview.util.matcher.KeyedMatcher;
+import jalview.util.matcher.KeyedMatcherI;
+import jalview.util.matcher.Matcher;
+import jalview.util.matcher.MatcherI;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.Dimension;
 import java.awt.FlowLayout;
+import java.awt.GridLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.FocusAdapter;
 import java.awt.event.FocusEvent;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
+import java.util.Iterator;
 
 import javax.swing.BorderFactory;
 import javax.swing.JCheckBox;
@@ -94,6 +102,18 @@ public class FeatureColourChooser extends JalviewDialog
 
   private ActionListener colourEditor = null;
 
+  private JComboBox<String> filterAttribute;
+
+  private JComboBox<Condition> filterCondition;
+
+  private JTextField filterValue;
+
+  private JComboBox<String> filterAttribute2;
+
+  private JComboBox<Condition> filterCondition2;
+
+  private JTextField filterValue2;
+
   /**
    * Constructor
    * 
@@ -213,62 +233,84 @@ public class FeatureColourChooser extends JalviewDialog
       threshline.value = cs.getThreshold();
     }
 
+    setInitialFilters(cs.getAttributeFilters());
+
     adjusting = false;
 
     changeColour(false);
     waitForInput();
   }
 
-  private void jbInit() throws Exception
+  /**
+   * Populates the attribute filter fields for the initial display
+   * 
+   * @param attributeFilters
+   */
+  void setInitialFilters(KeyedMatcherI attributeFilters)
   {
-
-    minColour.setFont(JvSwingUtils.getLabelFont());
-    minColour.setBorder(BorderFactory.createLineBorder(Color.black));
-    minColour.setPreferredSize(new Dimension(40, 20));
-    minColour.setToolTipText(MessageManager.getString("label.min_colour"));
-    minColour.addMouseListener(new MouseAdapter()
-    {
-      @Override
-      public void mousePressed(MouseEvent e)
+    // todo generalise to populate N conditions
+    
+    if (attributeFilters != null)
+    {
+      filterAttribute.setSelectedItem(attributeFilters.getKey());
+      filterCondition.setSelectedItem(attributeFilters.getMatcher()
+              .getCondition());
+      filterValue.setText(attributeFilters.getMatcher().getPattern());
+      
+      KeyedMatcherI second = attributeFilters.getSecondMatcher();
+      if (second != null)
       {
-        if (minColour.isEnabled())
-        {
-          minColour_actionPerformed();
-        }
+        // todo add OR/AND condition to gui
+        filterAttribute2.setSelectedItem(second.getKey());
+        filterCondition2
+                .setSelectedItem(second.getMatcher().getCondition());
+        filterValue2.setText(second.getMatcher().getPattern());
       }
-    });
-    maxColour.setFont(JvSwingUtils.getLabelFont());
-    maxColour.setBorder(BorderFactory.createLineBorder(Color.black));
-    maxColour.setPreferredSize(new Dimension(40, 20));
-    maxColour.setToolTipText(MessageManager.getString("label.max_colour"));
-    maxColour.addMouseListener(new MouseAdapter()
+    }
+  }
+
+  private void jbInit() throws Exception
+  {
+    this.setLayout(new GridLayout(4, 1));
+
+    JPanel colourByPanel = initColoursPanel();
+
+    JPanel thresholdPanel = initThresholdPanel();
+
+    JPanel okCancelPanel = initOkCancelPanel();
+
+    this.add(colourByPanel);
+    this.add(thresholdPanel);
+
+    /*
+     * add filter by attributes options only if we know any attributes
+     */
+    Iterator<String> attributes = FeatureAttributes.getInstance()
+            .getAttributes(type).iterator();
+    if (attributes.hasNext())
     {
-      @Override
-      public void mousePressed(MouseEvent e)
-      {
-        if (maxColour.isEnabled())
-        {
-          maxColour_actionPerformed();
-        }
-      }
-    });
-    maxColour.setBorder(new LineBorder(Color.black));
-    JLabel minText = new JLabel(MessageManager.getString("label.min"));
-    minText.setFont(JvSwingUtils.getLabelFont());
-    JLabel maxText = new JLabel(MessageManager.getString("label.max"));
-    maxText.setFont(JvSwingUtils.getLabelFont());
-    this.setLayout(new BorderLayout());
-    JPanel jPanel1 = new JPanel();
-    jPanel1.setBackground(Color.white);
-    JPanel jPanel2 = new JPanel();
-    jPanel2.setLayout(new FlowLayout());
-    jPanel2.setBackground(Color.white);
+      JPanel filtersPanel = initFiltersPanel(attributes);
+      this.add(filtersPanel);
+    }
+
+    this.add(okCancelPanel);
+  }
+
+  /**
+   * Lay out fields for threshold options
+   * 
+   * @return
+   */
+  protected JPanel initThresholdPanel()
+  {
+    JPanel thresholdPanel = new JPanel();
+    thresholdPanel.setLayout(new FlowLayout());
     threshold.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        threshold_actionPerformed();
+        changeColour(true);
       }
     });
     threshold.setToolTipText(MessageManager
@@ -280,8 +322,6 @@ public class FeatureColourChooser extends JalviewDialog
     threshold.addItem(MessageManager
             .getString("label.threshold_feature_below_threshold")); // index 2
 
-    JPanel jPanel3 = new JPanel();
-    jPanel3.setLayout(new FlowLayout());
     thresholdValue.addActionListener(new ActionListener()
     {
       @Override
@@ -308,7 +348,7 @@ public class FeatureColourChooser extends JalviewDialog
             MessageManager.getString("label.adjust_threshold"));
     thresholdValue.setEnabled(false);
     thresholdValue.setColumns(7);
-    jPanel3.setBackground(Color.white);
+    thresholdPanel.setBackground(Color.white);
     thresholdIsMin.setBackground(Color.white);
     thresholdIsMin
             .setText(MessageManager.getString("label.threshold_minmax"));
@@ -319,40 +359,101 @@ public class FeatureColourChooser extends JalviewDialog
       @Override
       public void actionPerformed(ActionEvent actionEvent)
       {
-        thresholdIsMin_actionPerformed();
+        changeColour(true);
       }
     });
-    colourByLabel.setBackground(Color.white);
-    colourByLabel
-            .setText(MessageManager.getString("label.colour_by_label"));
-    colourByLabel.setToolTipText(MessageManager.getString(
-            "label.display_features_same_type_different_label_using_different_colour"));
-    colourByLabel.addActionListener(new ActionListener()
+    thresholdPanel.add(threshold);
+    thresholdPanel.add(slider);
+    thresholdPanel.add(thresholdValue);
+    thresholdPanel.add(thresholdIsMin);
+    return thresholdPanel;
+  }
+
+  /**
+   * Lay out OK and Cancel buttons
+   * 
+   * @return
+   */
+  protected JPanel initOkCancelPanel()
+  {
+    JPanel okCancelPanel = new JPanel();
+    okCancelPanel.setBackground(Color.white);
+    okCancelPanel.add(ok);
+    okCancelPanel.add(cancel);
+    return okCancelPanel;
+  }
+
+  /**
+   * Lay out Colour by Label and min/max colour widgets
+   * 
+   * @return
+   */
+  protected JPanel initColoursPanel()
+  {
+    JPanel colourByPanel = new JPanel();
+    colourByPanel.setLayout(new FlowLayout());
+    colourByPanel.setBackground(Color.white);
+    minColour.setFont(JvSwingUtils.getLabelFont());
+    minColour.setBorder(BorderFactory.createLineBorder(Color.black));
+    minColour.setPreferredSize(new Dimension(40, 20));
+    minColour.setToolTipText(MessageManager.getString("label.min_colour"));
+    minColour.addMouseListener(new MouseAdapter()
     {
       @Override
-      public void actionPerformed(ActionEvent actionEvent)
+      public void mousePressed(MouseEvent e)
+      {
+        if (minColour.isEnabled())
+        {
+          minColour_actionPerformed();
+        }
+      }
+    });
+    maxColour.setFont(JvSwingUtils.getLabelFont());
+    maxColour.setBorder(BorderFactory.createLineBorder(Color.black));
+    maxColour.setPreferredSize(new Dimension(40, 20));
+    maxColour.setToolTipText(MessageManager.getString("label.max_colour"));
+    maxColour.addMouseListener(new MouseAdapter()
+    {
+      @Override
+      public void mousePressed(MouseEvent e)
       {
-        colourByLabel_actionPerformed();
+        if (maxColour.isEnabled())
+        {
+          maxColour_actionPerformed();
+        }
       }
     });
+    maxColour.setBorder(new LineBorder(Color.black));
+    JLabel minText = new JLabel(MessageManager.getString("label.min"));
+    minText.setFont(JvSwingUtils.getLabelFont());
+    JLabel maxText = new JLabel(MessageManager.getString("label.max"));
+    maxText.setFont(JvSwingUtils.getLabelFont());
 
     JPanel colourPanel = new JPanel();
     colourPanel.setBackground(Color.white);
-    jPanel1.add(ok);
-    jPanel1.add(cancel);
-    jPanel2.add(colourByLabel, BorderLayout.WEST);
-    jPanel2.add(colourPanel, BorderLayout.EAST);
     colourPanel.add(minText);
     colourPanel.add(minColour);
     colourPanel.add(maxText);
     colourPanel.add(maxColour);
-    this.add(jPanel3, BorderLayout.CENTER);
-    jPanel3.add(threshold);
-    jPanel3.add(slider);
-    jPanel3.add(thresholdValue);
-    jPanel3.add(thresholdIsMin);
-    this.add(jPanel1, BorderLayout.SOUTH);
-    this.add(jPanel2, BorderLayout.NORTH);
+    colourByPanel.add(colourByLabel, BorderLayout.WEST);
+    colourByPanel.add(colourPanel, BorderLayout.EAST);
+
+    colourByLabel.setBackground(Color.white);
+    colourByLabel
+            .setText(MessageManager.getString("label.colour_by_label"));
+    colourByLabel
+            .setToolTipText(MessageManager
+                    .getString("label.display_features_same_type_different_label_using_different_colour"));
+    colourByLabel.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent actionEvent)
+      {
+        changeColour(true);
+      }
+    });
+
+    return colourByPanel;
   }
 
   /**
@@ -406,6 +507,11 @@ public class FeatureColourChooser extends JalviewDialog
       return;
     }
 
+    if (!validateInputs())
+    {
+      return;
+    }
+
     boolean aboveThreshold = false;
     boolean belowThreshold = false;
     if (threshold.getSelectedIndex() == 1)
@@ -505,11 +611,100 @@ public class FeatureColourChooser extends JalviewDialog
       maxColour.setForeground(oldmaxColour);
       minColour.setForeground(oldminColour);
     }
+
+    /*
+     * add attribute filters if entered
+     */
+    if (filterAttribute != null)
+    {
+      setAttributeFilters(acg);
+    }
+
     fr.setColour(type, acg);
     cs = acg;
     ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview);
   }
 
+  /**
+   * Checks inputs are valid, and answers true if they are, else false. Also
+   * sets the colour of invalid inputs to red.
+   * 
+   * @return
+   */
+  boolean validateInputs()
+  {
+    // todo generalise to N filters
+    return isValidFilter(filterValue, filterCondition)
+            || isValidFilter(filterValue2, filterCondition2);
+  }
+
+  /**
+   * Answers true unless a numeric condition has been selected with a
+   * non-numeric value
+   * 
+   * @param value
+   * @param condition
+   */
+  protected boolean isValidFilter(JTextField value, JComboBox<Condition> condition)
+  {
+    if (value == null || condition == null)
+    {
+      return true; // fields not populated
+    }
+
+    value.setBackground(Color.white);
+    String v1 = value.getText().trim();
+    if (v1.length() > 0)
+    {
+      Condition c1 = (Condition) condition.getSelectedItem();
+      if (c1.isNumeric())
+      {
+        try
+        {
+          Float.valueOf(v1);
+        } catch (NumberFormatException e)
+        {
+          value.setBackground(Color.red);
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * Sets any attribute value filters entered in the dialog as filters on the
+   * colour scheme
+   * 
+   * @param acg
+   */
+  protected void setAttributeFilters(FeatureColourI acg)
+  {
+    String attribute = (String) filterAttribute.getSelectedItem();
+    Condition cond = (Condition) filterCondition.getSelectedItem();
+    String pattern = filterValue.getText().trim();
+    if (pattern.length() > 1)
+    {
+      MatcherI filter = new Matcher(cond, pattern);
+      KeyedMatcherI km = new KeyedMatcher(attribute, filter);
+
+      /*
+       * is there a second condition?
+       * todo: generalise to N conditions
+       */
+      pattern = filterValue2.getText().trim();
+      if (pattern.length() > 1)
+      {
+        attribute = (String) filterAttribute2.getSelectedItem();
+        cond = (Condition) filterCondition2.getSelectedItem();
+        filter = new Matcher(cond, pattern);
+        km = km.and(attribute, filter);
+      }
+      acg.setAttributeFilters(km);
+    }
+  }
+
   @Override
   protected void raiseClosed()
   {
@@ -544,14 +739,6 @@ public class FeatureColourChooser extends JalviewDialog
   }
 
   /**
-   * Action on change of choice of No / Above / Below Threshold
-   */
-  protected void threshold_actionPerformed()
-  {
-    changeColour(true);
-  }
-
-  /**
    * Action on text entry of a threshold value
    */
   protected void thresholdValue_actionPerformed()
@@ -594,16 +781,6 @@ public class FeatureColourChooser extends JalviewDialog
     changeColour(false);
   }
 
-  protected void thresholdIsMin_actionPerformed()
-  {
-    changeColour(true);
-  }
-
-  protected void colourByLabel_actionPerformed()
-  {
-    changeColour(true);
-  }
-
   void addActionListener(ActionListener graduatedColorEditor)
   {
     if (colourEditor != null)
@@ -629,4 +806,130 @@ public class FeatureColourChooser extends JalviewDialog
     return cs;
   }
 
+  /**
+   * Lay out fields for attribute value filters
+   * 
+   * @param attNames
+   * 
+   * @return
+   */
+  protected JPanel initFiltersPanel(Iterator<String> attNames)
+  {
+    JPanel filtersPanel = new JPanel();
+    filtersPanel.setLayout(new GridLayout(2, 3));
+    filtersPanel.setBackground(Color.white);
+
+    /*
+     * drop-down choice of attribute
+     */
+    filterAttribute = new JComboBox<>();
+    filterAttribute2 = new JComboBox<>();
+    while (attNames.hasNext())
+    {
+      String attName = attNames.next();
+      filterAttribute.addItem(attName);
+      filterAttribute2.addItem(attName);
+    }
+    filterAttribute.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        changeColour(true);
+      }
+    });
+
+    /*
+     * drop-down choice of test condition
+     */
+    filterCondition = new JComboBox<>();
+    for (Condition cond : Condition.values())
+    {
+      filterCondition.addItem(cond);
+    }
+    filterCondition.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        changeColour(true);
+      }
+    });
+
+    filterValue = new JTextField(12);
+    filterValue.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        changeColour(true);
+      }
+    });
+    filterValue.addFocusListener(new FocusAdapter()
+    {
+      @Override
+      public void focusLost(FocusEvent e)
+      {
+        changeColour(true);
+      }
+    });
+
+    /*
+     * repeat for a second filter
+     * todo: generalise to N filters
+     */
+    filterAttribute2.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        changeColour(true);
+      }
+    });
+
+    /*
+     * drop-down choice of test condition
+     */
+    filterCondition2 = new JComboBox<>();
+    for (Condition cond : Condition.values())
+    {
+      filterCondition2.addItem(cond);
+    }
+    filterCondition2.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        changeColour(true);
+      }
+    });
+
+    filterValue2 = new JTextField(12);
+    filterValue2.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
+      {
+        changeColour(true);
+      }
+    });
+    filterValue2.addFocusListener(new FocusAdapter()
+    {
+      @Override
+      public void focusLost(FocusEvent e)
+      {
+        changeColour(true);
+      }
+    });
+
+    filtersPanel.add(filterAttribute);
+    filtersPanel.add(filterCondition);
+    filtersPanel.add(filterValue);
+    filtersPanel.add(filterAttribute2);
+    filtersPanel.add(filterCondition2);
+    filtersPanel.add(filterValue2);
+
+    return filtersPanel;
+  }
+
 }