Merge branch 'develop' into trialMerge
[jalview.git] / src / jalview / gui / FeatureTypeSettings.java
index 0b8b2b2..50efffc 100644 (file)
@@ -22,6 +22,7 @@ package jalview.gui;
 
 import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureColourI;
+import jalview.bin.Cache;
 import jalview.datamodel.GraphLine;
 import jalview.datamodel.features.FeatureAttributes;
 import jalview.datamodel.features.FeatureAttributes.Datatype;
@@ -30,6 +31,7 @@ import jalview.datamodel.features.FeatureMatcherI;
 import jalview.datamodel.features.FeatureMatcherSet;
 import jalview.datamodel.features.FeatureMatcherSetI;
 import jalview.io.gff.SequenceOntologyFactory;
+import jalview.io.gff.SequenceOntologyI;
 import jalview.schemes.FeatureColour;
 import jalview.util.ColorUtils;
 import jalview.util.MessageManager;
@@ -51,6 +53,7 @@ import java.awt.event.MouseEvent;
 import java.text.DecimalFormat;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -68,11 +71,10 @@ import javax.swing.JPanel;
 import javax.swing.JRadioButton;
 import javax.swing.JSlider;
 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
@@ -172,7 +174,7 @@ public class FeatureTypeSettings extends JalviewDialog
 
   private JPanel maxColour = new JPanel();
 
-  private JComboBox<String> threshold = new JComboBox<>();
+  private JComboBox<Object> threshold = new JComboBox<>();
 
   private JSlider slider = new JSlider();
 
@@ -189,17 +191,17 @@ public class FeatureTypeSettings extends JalviewDialog
   /*
    * choice of option for 'colour for no value'
    */
-  private JComboBox<String> noValueCombo;
+  private JComboBox<Object> noValueCombo;
 
   /*
    * choice of what to colour by text (Label or attribute)
    */
-  private JComboBox<String> colourByTextCombo;
+  private JComboBox<Object> colourByTextCombo;
 
   /*
    * choice of what to colour by range (Score or attribute)
    */
-  private JComboBox<String> colourByRangeCombo;
+  private JComboBox<Object> colourByRangeCombo;
 
   private JRadioButton andFilters;
 
@@ -213,19 +215,30 @@ public class FeatureTypeSettings extends JalviewDialog
   private JPanel chooseFiltersPanel;
 
   /*
-   * feature types present in Feature Renderer which are
-   * sub-types of the one this editor is acting on
+   * the root Sequence Ontology terms (if any) that is a parent of
+   * the current feature type
    */
-  private final List<String> subTypes;
+  private String rootSOTerm;
+
+  /*
+   * a map whose keys are Sequence Ontology terms - selected from the
+   * current term and its parents in the SO - whose subterms include
+   * additional feature types; the map entry is the list of additional
+   * feature types that match the key or have it as a parent term; in
+   * other words, distinct 'aggregations' that include the current feature type
+   */
+  private final Map<String, List<String>> relatedSoTerms;
 
   /*
    * if true, filter or colour settings are also applied to 
-   * any feature sub-types in the Sequence Ontology
+   * any sub-types of parentTerm in the Sequence Ontology
    */
   private boolean applyFiltersToSubtypes;
 
   private boolean applyColourToSubtypes;
 
+  private String parentSOTerm;
+
   /**
    * Constructor
    * 
@@ -238,30 +251,29 @@ public class FeatureTypeSettings extends JalviewDialog
     this.featureType = theType;
     ap = fr.ap;
 
-    /*
-     * determine sub-types (if any) of this feature type
-     */
-    List<String> types = fr.getRenderOrder();
-    subTypes = SequenceOntologyFactory.getInstance()
-            .getChildTerms(this.featureType, types);
-    Collections.sort(subTypes); // sort for ease of reading in tooltip
+    SequenceOntologyI so = SequenceOntologyFactory.getInstance();
+    relatedSoTerms = so.findSequenceOntologyGroupings(
+            this.featureType, fr.getRenderOrder());
 
     /*
-     * save original colours and filters for this feature type
-     * and any sub-types, to restore on Cancel
+     * save original colours and filters for this feature type,
+     * and any related types, to restore on Cancel
      */
     originalFilters = new HashMap<>();
     originalFilters.put(theType, fr.getFeatureFilter(theType));
     originalColours = new HashMap<>();
     originalColours.put(theType, fr.getFeatureColours().get(theType));
-    for (String child : subTypes)
+    for (List<String> related : relatedSoTerms.values())
     {
-      originalFilters.put(child, fr.getFeatureFilter(child));
-      originalColours.put(child, fr.getFeatureColours().get(child));
+      for (String type : related)
+      {
+        originalFilters.put(type, fr.getFeatureFilter(type));
+        originalColours.put(type, fr.getFeatureColours().get(type));
+      }
     }
 
     adjusting = true;
-    
+
     try
     {
       initialise();
@@ -270,15 +282,15 @@ public class FeatureTypeSettings extends JalviewDialog
       ex.printStackTrace();
       return;
     }
-    
+
     updateColoursTab();
-    
+
     updateFiltersTab();
-    
+
     adjusting = false;
-    
+
     colourChanged(false);
-    
+
     String title = MessageManager
             .formatMessage("label.display_settings_for", new String[]
             { theType });
@@ -287,6 +299,60 @@ public class FeatureTypeSettings extends JalviewDialog
   }
 
   /**
+   * Answers a (possibly empty) map of any Sequence Ontology terms (the current
+   * feature type and its parents) which incorporate additional known feature
+   * types (the map entry).
+   * <p>
+   * For example if {@code stop_gained} and {@code stop_lost} are known feature
+   * types, then SO term {@ nonsynonymous_variant} is the first common parent of
+   * both terms
+   * 
+   * @param featureType
+   *          the current feature type being configured
+   * @param featureTypes
+   *          all known feature types on the alignment
+   * @return
+   */
+  protected static Map<String, List<String>> findSequenceOntologyGroupings(
+          String featureType, List<String> featureTypes)
+  {
+    List<String> sortedTypes = new ArrayList<>(featureTypes);
+    Collections.sort(sortedTypes);
+
+    Map<String, List<String>> parents = new HashMap<>();
+
+    /*
+     * method: 
+     * walk up featureType and all of its parents
+     * find other feature types which are subsumed by each term
+     * add each distinct aggregation of included feature types to the map
+     */
+    List<String> candidates = new ArrayList<>();
+    SequenceOntologyI so = SequenceOntologyFactory.getInstance();
+    candidates.add(featureType);
+    while (!candidates.isEmpty())
+    {
+      String term = candidates.remove(0);
+      List<String> includedFeatures = new ArrayList<>();
+      for (String type : sortedTypes)
+      {
+        if (!type.equals(featureType) && so.isA(type, term))
+        {
+          includedFeatures.add(type);
+        }
+      }
+      if (!includedFeatures.isEmpty()
+              && !parents.containsValue(includedFeatures))
+      {
+        parents.put(term, includedFeatures);
+      }
+      candidates.addAll(so.getParents(term));
+    }
+    
+    return parents;
+  }
+
+  /**
    * Configures the widgets on the Colours tab according to the current feature
    * colour scheme
    */
@@ -771,9 +837,9 @@ public class FeatureTypeSettings extends JalviewDialog
             MessageManager.getString("action.colour"), true);
 
     /*
-     * option to apply colour to sub-types as well (if there are any)
+     * option to apply colour to other selected types as well
      */
-    if (!subTypes.isEmpty())
+    if (!relatedSoTerms.isEmpty())
     {
       applyColourToSubtypes = false;
       colourByPanel.add(initSubtypesPanel(false));
@@ -878,8 +944,8 @@ public class FeatureTypeSettings extends JalviewDialog
   }
 
   /**
-   * Constructs and returns a panel with a checkbox for the option to apply any
-   * changes also to sub-types of the feature type
+   * Constructs and returns a panel with the option to apply any changes also to
+   * sub-types of SO terms at or above the feature type
    * 
    * @return
    */
@@ -887,10 +953,48 @@ public class FeatureTypeSettings extends JalviewDialog
   {
     JPanel toSubtypes = new JPanel(new FlowLayout(FlowLayout.LEFT));
     toSubtypes.setBackground(Color.WHITE);
+
+    /*
+     * checkbox 'apply to sub-types of...'
+     */
     JCheckBox applyToSubtypesCB = new JCheckBox(MessageManager
-            .formatMessage("label.apply_to_subtypes", featureType));
-    applyToSubtypesCB.setToolTipText(getSubtypesTooltip());
-    applyToSubtypesCB.addActionListener(new ActionListener()
+            .formatMessage("label.apply_to_subtypes", rootSOTerm));
+    toSubtypes.add(applyToSubtypesCB);
+    toSubtypes
+            .setToolTipText(MessageManager.getString("label.group_by_so"));
+
+    /*
+     * combobox to choose 'parent' of sub-types
+     */
+    List<String> soTerms = new ArrayList<>();
+    for (String term : relatedSoTerms.keySet())
+    {
+      soTerms.add(term);
+    }
+    // sort from most restrictive to most inclusive
+    Collections.sort(soTerms, new Comparator<String>()
+    {
+      @Override
+      public int compare(String o1, String o2)
+      {
+        return Integer.compare(relatedSoTerms.get(o1).size(),
+                relatedSoTerms.get(o2).size());
+      }
+    });
+    List<String> tooltips = new ArrayList<>();
+    for (String term : soTerms)
+    {
+      tooltips.add(getSOTermsTooltip(relatedSoTerms.get(term)));
+    }
+    JComboBox<String> parentType = JvSwingUtils
+            .buildComboWithTooltips(soTerms, tooltips);
+    toSubtypes.add(parentType);
+
+    /*
+     * on toggle of checkbox, or change of parent SO term,
+     * reset and then reapply filters to the selected scope
+     */
+    final ActionListener action = new ActionListener()
     {
       /*
        * reset and reapply settings on toggle of checkbox
@@ -898,6 +1002,7 @@ public class FeatureTypeSettings extends JalviewDialog
       @Override
       public void actionPerformed(ActionEvent e)
       {
+        parentSOTerm = (String) parentType.getSelectedItem();
         if (forFilters)
         {
           applyFiltersToSubtypes = applyToSubtypesCB.isSelected();
@@ -911,8 +1016,9 @@ public class FeatureTypeSettings extends JalviewDialog
           colourChanged(true);
         }
       }
-    });
-    toSubtypes.add(applyToSubtypesCB);
+    };
+    applyToSubtypesCB.addActionListener(action);
+    parentType.addActionListener(action);
 
     return toSubtypes;
   }
@@ -961,7 +1067,7 @@ public class FeatureTypeSettings extends JalviewDialog
     fr.setColour(featureType, acg);
     if (applyColourToSubtypes)
     {
-      for (String child : subTypes)
+      for (String child : relatedSoTerms.get(parentSOTerm))
       {
         fr.setColour(child, acg);
       }
@@ -1115,6 +1221,10 @@ public class FeatureTypeSettings extends JalviewDialog
     ap.paintAlignment(true, true);
   }
 
+  /**
+   * Restores filters for all feature types to their values when the dialog was
+   * opened
+   */
   protected void restoreOriginalFilters()
   {
     for (Entry<String, FeatureMatcherSetI> entry : originalFilters
@@ -1124,6 +1234,10 @@ public class FeatureTypeSettings extends JalviewDialog
     }
   }
 
+  /**
+   * Restores colours for all feature types to their values when the dialog was
+   * opened
+   */
   protected void restoreOriginalColours()
   {
     for (Entry<String, FeatureColourI> entry : originalColours.entrySet())
@@ -1145,7 +1259,7 @@ public class FeatureTypeSettings extends JalviewDialog
        */
       adjusting = true;
       float f = Float.parseFloat(thresholdValue.getText());
-      f = Float.max(f,  this.min);
+      f = Float.max(f, this.min);
       f = Float.min(f, this.max);
       thresholdValue.setText(String.valueOf(f));
       slider.setValue((int) (f * scaleFactor));
@@ -1220,7 +1334,7 @@ public class FeatureTypeSettings extends JalviewDialog
    * @param withRange
    * @param withText
    */
-  protected JComboBox<String> populateAttributesDropdown(
+  protected JComboBox<Object> populateAttributesDropdown(
           List<String[]> attNames, boolean withRange, boolean withText)
   {
     List<String> displayAtts = new ArrayList<>();
@@ -1259,9 +1373,11 @@ public class FeatureTypeSettings extends JalviewDialog
       tooltips.add(desc == null ? "" : desc);
     }
 
-    JComboBox<String> attCombo = JvSwingUtils
-            .buildComboWithTooltips(displayAtts, tooltips);
-
+    // now convert String List to Object List for buildComboWithTooltips
+    List<Object> displayAttsObjects = new ArrayList<>(displayAtts);
+    JComboBox<Object> attCombo = JvSwingUtils
+            .buildComboWithTooltips(displayAttsObjects, tooltips);
+    
     return attCombo;
   }
 
@@ -1277,9 +1393,9 @@ public class FeatureTypeSettings extends JalviewDialog
     outerPanel.setBackground(Color.white);
 
     /*
-     * option to apply colour to sub-types as well (if there are any)
+     * option to apply colour to other selected types as well
      */
-    if (!subTypes.isEmpty())
+    if (!relatedSoTerms.isEmpty())
     {
       applyFiltersToSubtypes = false;
       outerPanel.add(initSubtypesPanel(true));
@@ -1316,10 +1432,6 @@ public class FeatureTypeSettings extends JalviewDialog
     JPanel andOrPanel = new JPanel(new BorderLayout());
     andOrPanel.setBackground(Color.white);
 
-//    JPanel panel1 = new JPanel(new FlowLayout(FlowLayout.LEFT));
-//    andOrPanel.add(panel1, BorderLayout.WEST);
-//    panel1.setBackground(Color.white);
-//    panel1.setBorder(BorderFactory.createLineBorder(debugBorderColour));
     andFilters = new JRadioButton(MessageManager.getString("label.and"));
     orFilters = new JRadioButton(MessageManager.getString("label.or"));
     ActionListener actionListener = new ActionListener()
@@ -1345,16 +1457,18 @@ public class FeatureTypeSettings extends JalviewDialog
   }
 
   /**
-   * Builds a tooltip for the 'Apply to subtypes' checkbox with a list of
-   * subtypes of this feature type
+   * Builds a tooltip for the 'Apply also to...' combobox with a list of known
+   * feature types (excluding the current type) which are sub-types of the
+   * selected Sequence Ontology term
    * 
+   * @param
    * @return
    */
-  protected String getSubtypesTooltip()
+  protected String getSOTermsTooltip(List<String> list)
   {
-    StringBuilder sb = new StringBuilder(20 * subTypes.size());
+    StringBuilder sb = new StringBuilder(20 * relatedSoTerms.size());
     sb.append(MessageManager.getString("label.apply_also_to"));
-    for (String child : subTypes)
+    for (String child : list)
     {
       sb.append("<br>").append(child);
     }
@@ -1470,7 +1584,7 @@ public class FeatureTypeSettings extends JalviewDialog
      * drop-down choice of attribute, with description as a tooltip 
      * if we can obtain it
      */
-    final JComboBox<String> attCombo = populateAttributesDropdown(attNames,
+    final JComboBox<Object> attCombo = populateAttributesDropdown(attNames,
             true, true);
     String filterBy = setSelectedAttribute(attCombo, filter);
 
@@ -1572,10 +1686,10 @@ public class FeatureTypeSettings extends JalviewDialog
     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.setToolTipText(
+              MessageManager.getString("label.delete_condition"));
+      removeCondition.setBorder(new EmptyBorder(0, 0, 0, 0));
       removeCondition.addActionListener(new ActionListener()
       {
         @Override
@@ -1598,7 +1712,7 @@ public class FeatureTypeSettings extends JalviewDialog
    * @param attCombo
    * @param filter
    */
-  private String setSelectedAttribute(JComboBox<String> attCombo,
+  private String setSelectedAttribute(JComboBox<Object> attCombo,
           FeatureMatcherI filter)
   {
     String item = null;
@@ -1815,11 +1929,19 @@ public class FeatureTypeSettings extends JalviewDialog
    * @param valueField
    * @param filterIndex
    */
-  protected boolean updateFilter(JComboBox<String> attCombo,
+  protected boolean updateFilter(JComboBox<Object> attCombo,
           JComboBox<Condition> condCombo, JTextField valueField,
           int filterIndex)
   {
-    String attName = (String) attCombo.getSelectedItem();
+    String attName;
+    try
+    {
+      attName = (String) attCombo.getSelectedItem();
+    } catch (Exception e)
+    {
+      Cache.log.error("Problem casting Combo box entry to String");
+      attName = attCombo.getSelectedItem().toString();
+    }
     Condition cond = (Condition) condCombo.getSelectedItem();
     String pattern = valueField.getText().trim();
 
@@ -1898,7 +2020,7 @@ public class FeatureTypeSettings extends JalviewDialog
     fr.setFeatureFilter(featureType, combined.isEmpty() ? null : combined);
     if (applyFiltersToSubtypes)
     {
-      for (String child : subTypes)
+      for (String child : relatedSoTerms.get(parentSOTerm))
       {
         fr.setFeatureFilter(child, combined.isEmpty() ? null : combined);
       }