JAL-3763 refactored getCoveringMapping for virtual feature discovery
[jalview.git] / src / jalview / viewmodel / seqfeatures / FeatureRendererModel.java
index 2324a64..8f513c9 100644 (file)
  */
 package jalview.viewmodel.seqfeatures;
 
+import java.awt.Color;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
 import jalview.api.AlignViewportI;
 import jalview.api.FeatureColourI;
 import jalview.api.FeaturesDisplayedI;
 import jalview.datamodel.AlignedCodonFrame;
+import jalview.datamodel.AlignedCodonFrame.SequenceToSequenceMapping;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.Mapping;
+import jalview.datamodel.MappedFeatures;
 import jalview.datamodel.SearchResultMatchI;
 import jalview.datamodel.SearchResults;
 import jalview.datamodel.SearchResultsI;
@@ -37,20 +52,6 @@ import jalview.renderer.seqfeatures.FeatureRenderer;
 import jalview.schemes.FeatureColour;
 import jalview.util.ColorUtils;
 
-import java.awt.Color;
-import java.beans.PropertyChangeListener;
-import java.beans.PropertyChangeSupport;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
 public abstract class FeatureRendererModel
         implements jalview.api.FeatureRenderer
 {
@@ -101,11 +102,11 @@ public abstract class FeatureRendererModel
 
   Map<String, Float> featureOrder = null;
 
-  protected PropertyChangeSupport changeSupport = new PropertyChangeSupport(
-          this);
-
   protected AlignViewportI av;
 
+  private PropertyChangeSupport changeSupport = new PropertyChangeSupport(
+          this);
+
   @Override
   public AlignViewportI getViewport()
   {
@@ -298,13 +299,19 @@ public abstract class FeatureRendererModel
       {
         firing = Boolean.TRUE;
         findAllFeatures(true); // add all new features as visible
-        changeSupport.firePropertyChange("changeSupport", null, null);
+        notifyFeaturesChanged();
         firing = Boolean.FALSE;
       }
     }
   }
 
   @Override
+  public void notifyFeaturesChanged()
+  {
+    changeSupport.firePropertyChange("changeSupport", null, null);
+  }
+
+  @Override
   public List<SequenceFeature> findFeaturesAtColumn(SequenceI sequence, int column)
   {
     /*
@@ -657,7 +664,7 @@ public abstract class FeatureRendererModel
     {
       featureOrder = new Hashtable<>();
     }
-    featureOrder.put(type, new Float(position));
+    featureOrder.put(type, Float.valueOf(position));
     return position;
   }
 
@@ -857,7 +864,7 @@ public abstract class FeatureRendererModel
     }
     if (newGroupsVisible)
     {
-      featureGroups.put(group, new Boolean(true));
+      featureGroups.put(group, Boolean.valueOf(true));
       return true;
     }
     return false;
@@ -893,7 +900,7 @@ public abstract class FeatureRendererModel
   @Override
   public void setGroupVisibility(String group, boolean visible)
   {
-    featureGroups.put(group, new Boolean(visible));
+    featureGroups.put(group, Boolean.valueOf(visible));
   }
 
   @Override
@@ -905,7 +912,7 @@ public abstract class FeatureRendererModel
       for (String gst : toset)
       {
         Boolean st = featureGroups.get(gst);
-        featureGroups.put(gst, new Boolean(visible));
+        featureGroups.put(gst, Boolean.valueOf(visible));
         if (st != null)
         {
           rdrw = rdrw || (visible != st.booleanValue());
@@ -1033,11 +1040,11 @@ public abstract class FeatureRendererModel
   }
 
   /**
-   * Removes from the list of features any that duplicate the location of a
-   * feature of the same type. Should be used only for features of the same,
-   * simple, feature colour (which normally implies the same feature type). Does
-   * not check visibility settings for feature type or feature group. No
-   * filtering is done if transparency, or any feature filters, are in force.
+   * Removes from the list of features any whose group is not shown, or that are
+   * visible and duplicate the location of a visible feature of the same type.
+   * Should be used only for features of the same, simple, feature colour (which
+   * normally implies the same feature type). No filtering is done if
+   * transparency, or any feature filters, are in force.
    * 
    * @param features
    */
@@ -1061,6 +1068,11 @@ public abstract class FeatureRendererModel
     while (it.hasNext())
     {
       SequenceFeature sf = it.next();
+      if (featureGroupNotShown(sf))
+      {
+        it.remove();
+        continue;
+      }
 
       /*
        * a feature is redundant for rendering purposes if it has the
@@ -1068,7 +1080,8 @@ public abstract class FeatureRendererModel
        * (checking type and isContactFeature as a fail-safe here, although
        * currently they are guaranteed to match in this context)
        */
-      if (lastFeature != null && sf.getBegin() == lastFeature.getBegin()
+      if (lastFeature != null
+              && sf.getBegin() == lastFeature.getBegin()
               && sf.getEnd() == lastFeature.getEnd()
               && sf.isContactFeature() == lastFeature.isContactFeature()
               && sf.getType().equals(lastFeature.getType()))
@@ -1154,43 +1167,54 @@ public abstract class FeatureRendererModel
     return filter == null ? true : filter.matches(sf);
   }
 
-  /**
-   * Answers a (possibly empty) list of features in this alignment at a position
-   * (or range) which is mappable from the given sequence residue position in a
-   * mapped alignment. Features are returned in render order of feature type (on
-   * top last), with order within feature type undefined.
-   * 
-   * @param sequence
-   * @param pos
-   * @return
-   */
-  public List<SequenceFeature> findComplementFeaturesAtResidue(SequenceI sequence, int pos)
+  @Override
+  public MappedFeatures findComplementFeaturesAtResidue(
+          final SequenceI sequence, final int pos)
   {
     SequenceI ds = sequence.getDatasetSequence();
+    if (ds == null)
+    {
+      ds = sequence;
+    }
+    final char residue = ds.getCharAt(pos - ds.getStart());
+
     List<SequenceFeature> found = new ArrayList<>();
     List<AlignedCodonFrame> mappings = this.av.getAlignment()
             .getCodonFrame(sequence);
 
     /*
+     * fudge: if no mapping found, check the complementary alignment
+     * todo: only store in one place? StructureSelectionManager?
+     */
+    if (mappings.isEmpty())
+    {
+      mappings = this.av.getCodingComplement().getAlignment()
+              .getCodonFrame(sequence);
+    }
+
+    /*
      * todo: direct lookup of CDS for peptide and vice-versa; for now,
      * have to search through an unordered list of mappings for a candidate
      */
+    SequenceToSequenceMapping mapping = null;
+    SequenceI mapFrom = null;
+
     for (AlignedCodonFrame acf : mappings)
     {
-      Mapping mapping = acf.getMappingForSequence(sequence, true);
-      if (mapping == null || mapping.getMap().getFromRatio() == mapping
-              .getMap().getToRatio())
+      mapping = acf.getCoveringCodonMapping(ds);
+      if (mapping == null)
       {
-        continue; // we are only looking for 3:1 or 1:3 mappings
+        continue;
       }
       SearchResultsI sr = new SearchResults();
-      acf.markMappedRegion(ds, pos, sr);
+      mapping.markMappedRegion(ds, pos, sr);
       for (SearchResultMatchI match : sr.getResults())
       {
         int fromRes = match.getStart();
         int toRes = match.getEnd();
+        mapFrom = match.getSequence();
         List<SequenceFeature> fs = findFeaturesAtResidue(
-                match.getSequence(), fromRes, toRes);
+                mapFrom, fromRes, toRes);
         for (SequenceFeature sf : fs)
         {
           if (!found.contains(sf))
@@ -1199,11 +1223,27 @@ public abstract class FeatureRendererModel
           }
         }
       }
+
+      /*
+       * just take the first mapped features we find
+       */
+      if (!found.isEmpty())
+      {
+        break;
+      }
+    }
+    if (found.isEmpty())
+    {
+      return null;
     }
+
     /*
-     * sort by renderorder, inefficiently
+     * sort by renderorder (inefficiently but ok for small scale);
+     * NB this sorts 'on top' feature to end, for rendering
      */
     List<SequenceFeature> result = new ArrayList<>();
+    final int toAdd = found.size();
+    int added = 0;
     for (String type : renderOrder)
     {
       for (SequenceFeature sf : found)
@@ -1211,15 +1251,44 @@ public abstract class FeatureRendererModel
         if (type.equals(sf.getType()))
         {
           result.add(sf);
-          if (result.size() == found.size())
-          {
-            return result;
-          }
+          added++;
+        }
+        if (added == toAdd)
+        {
+          break;
         }
       }
     }
     
-    return result;
+    return new MappedFeatures(mapping.getMapping(), mapFrom, pos, residue, result);
+  }
+
+  @Override
+  public boolean isVisible(SequenceFeature feature)
+  {
+    if (feature == null)
+    {
+      return false;
+    }
+    if (getFeaturesDisplayed() == null
+            || !getFeaturesDisplayed().isVisible(feature.getType()))
+    {
+      return false;
+    }
+    if (featureGroupNotShown(feature))
+    {
+      return false;
+    }
+    FeatureColourI fc = featureColours.get(feature.getType());
+    if (fc != null && fc.isOutwithThreshold(feature))
+    {
+      return false;
+    }
+    if (!featureMatchesFilters(feature))
+    {
+      return false;
+    }
+    return true;
   }
 
 }