Merge branch 'releases/Release_2_11_3_Branch'
[jalview.git] / src / jalview / viewmodel / seqfeatures / FeatureRendererModel.java
index 54bb4ee..e812ed5 100644 (file)
  */
 package jalview.viewmodel.seqfeatures;
 
-import jalview.api.AlignViewportI;
-import jalview.api.FeatureColourI;
-import jalview.api.FeaturesDisplayedI;
-import jalview.datamodel.AlignmentI;
-import jalview.datamodel.SequenceFeature;
-import jalview.datamodel.SequenceI;
-import jalview.datamodel.features.FeatureMatcherSetI;
-import jalview.datamodel.features.SequenceFeatures;
-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;
@@ -47,6 +35,25 @@ 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.MappedFeatures;
+import jalview.datamodel.SearchResultMatchI;
+import jalview.datamodel.SearchResults;
+import jalview.datamodel.SearchResultsI;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+import jalview.datamodel.features.FeatureMatcherSetI;
+import jalview.datamodel.features.SequenceFeatures;
+import jalview.renderer.seqfeatures.FeatureRenderer;
+import jalview.schemes.FeatureColour;
+import jalview.util.ColorUtils;
+import jalview.util.Platform;
+
 public abstract class FeatureRendererModel
         implements jalview.api.FeatureRenderer
 {
@@ -97,11 +104,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()
   {
@@ -294,14 +301,21 @@ 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 List<SequenceFeature> findFeaturesAtColumn(SequenceI sequence, int column)
+  public void notifyFeaturesChanged()
+  {
+    changeSupport.firePropertyChange("changeSupport", null, null);
+  }
+
+  @Override
+  public List<SequenceFeature> findFeaturesAtColumn(SequenceI sequence,
+          int column)
   {
     /*
      * include features at the position provided their feature type is 
@@ -321,12 +335,12 @@ public abstract class FeatureRendererModel
             visibleTypes);
 
     /*
-     * include features unless their feature group is not displayed, or
-     * they are hidden (have no colour) based on a filter or colour threshold
+     * include features unless they are hidden (have no colour), based on 
+     * feature group visibility, or a filter or colour threshold
      */
     for (SequenceFeature sf : features)
     {
-      if (!featureGroupNotShown(sf) && getColour(sf) != null)
+      if (getColour(sf) != null)
       {
         result.add(sf);
       }
@@ -395,8 +409,8 @@ public abstract class FeatureRendererModel
         }
         if (groupDisplayed)
         {
-          Set<String> types = asq.getFeatures().getFeatureTypesForGroups(
-                  true, group);
+          Set<String> types = asq.getFeatures()
+                  .getFeatureTypesForGroups(true, group);
           for (String type : types)
           {
             if (!allfeatures.contains(type)) // or use HashSet and no test?
@@ -475,6 +489,7 @@ public abstract class FeatureRendererModel
       }
     }
   }
+
   protected Boolean firing = Boolean.FALSE;
 
   /**
@@ -615,8 +630,9 @@ public abstract class FeatureRendererModel
    */
   public boolean showFeatureOfType(String type)
   {
-    return type == null ? false : (av.getFeaturesDisplayed() == null ? true
-            : av.getFeaturesDisplayed().isVisible(type));
+    return type == null ? false
+            : (av.getFeaturesDisplayed() == null ? true
+                    : av.getFeaturesDisplayed().isVisible(type));
   }
 
   @Override
@@ -694,15 +710,15 @@ public abstract class FeatureRendererModel
   }
 
   /**
-   * Sets the priority order for features, with the highest priority (displayed on
-   * top) at the start of the data array
+   * Sets the priority order for features, with the highest priority (displayed
+   * on top) at the start of the data array
    * 
    * @param data
    *          an array of { Type, Colour, Filter, Boolean }
    * @param visibleNew
    *          when true current featureDisplay list will be cleared
-   * @return true if any visible features have been reordered or recoloured, else
-   *         false (i.e. no need to repaint)
+   * @return true if any visible features have been reordered or recoloured,
+   *         else false (i.e. no need to repaint)
    */
   public boolean setFeaturePriority(FeatureSettingsBean[] data,
           boolean visibleNew)
@@ -984,10 +1000,9 @@ public abstract class FeatureRendererModel
    * @param sequenceFeature
    * @return
    */
-  protected boolean featureGroupNotShown(final SequenceFeature sequenceFeature)
+  public boolean featureGroupNotShown(final SequenceFeature sequenceFeature)
   {
-    return featureGroups != null
-            && sequenceFeature.featureGroup != null
+    return featureGroups != null && sequenceFeature.featureGroup != null
             && sequenceFeature.featureGroup.length() != 0
             && featureGroups.containsKey(sequenceFeature.featureGroup)
             && !featureGroups.get(sequenceFeature.featureGroup)
@@ -999,7 +1014,7 @@ public abstract class FeatureRendererModel
    */
   @Override
   public List<SequenceFeature> findFeaturesAtResidue(SequenceI sequence,
-          int resNo)
+          int fromResNo, int toResNo)
   {
     List<SequenceFeature> result = new ArrayList<>();
     if (!av.areFeaturesDisplayed() || getFeaturesDisplayed() == null)
@@ -1012,13 +1027,12 @@ public abstract class FeatureRendererModel
      * displayed, and feature group is null or the empty string
      * or marked for display
      */
-    Set<String> visibleFeatures = getFeaturesDisplayed()
-            .getVisibleFeatures();
+    List<String> visibleFeatures = getDisplayedFeatureTypes();
     String[] visibleTypes = visibleFeatures
             .toArray(new String[visibleFeatures.size()]);
-    List<SequenceFeature> features = sequence.getFeatures().findFeatures(
-            resNo, resNo, visibleTypes);
-  
+    List<SequenceFeature> features = sequence.getFeatures()
+            .findFeatures(fromResNo, toResNo, visibleTypes);
+
     for (SequenceFeature sf : features)
     {
       if (!featureGroupNotShown(sf) && getColour(sf) != null)
@@ -1041,6 +1055,14 @@ public abstract class FeatureRendererModel
   public void filterFeaturesForDisplay(List<SequenceFeature> features)
   {
     /*
+     * fudge: JalviewJS's IntervalStore lacks the sort method called :-(
+     */
+    if (Platform.isJS())
+    {
+      return;
+    }
+
+    /*
      * don't remove 'redundant' features if 
      * - transparency is applied (feature count affects depth of feature colour)
      * - filters are applied (not all features may be displayable)
@@ -1070,8 +1092,7 @@ 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()))
@@ -1101,7 +1122,8 @@ public abstract class FeatureRendererModel
   }
 
   @Override
-  public void setFeatureFilter(String featureType, FeatureMatcherSetI filter)
+  public void setFeatureFilter(String featureType,
+          FeatureMatcherSetI filter)
   {
     if (filter == null || filter.isEmpty())
     {
@@ -1116,7 +1138,7 @@ public abstract class FeatureRendererModel
   /**
    * Answers the colour for the feature, or null if the feature is excluded by
    * feature group visibility, by filters, or by colour threshold settings. This
-   * method does not take feature visibility into account.
+   * method does not take feature type visibility into account.
    * 
    * @param sf
    * @param fc
@@ -1139,7 +1161,7 @@ public abstract class FeatureRendererModel
     {
       return null;
     }
-  
+
     return fc.getColor(sf);
   }
 
@@ -1185,6 +1207,103 @@ public abstract class FeatureRendererModel
   }
 
   @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 = acf.getCoveringCodonMapping(ds);
+      if (mapping == null)
+      {
+        continue;
+      }
+      SearchResultsI sr = new SearchResults();
+      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(mapFrom, fromRes,
+                toRes);
+        for (SequenceFeature sf : fs)
+        {
+          if (!found.contains(sf))
+          {
+            found.add(sf);
+          }
+        }
+      }
+
+      /*
+       * just take the first mapped features we find
+       */
+      if (!found.isEmpty())
+      {
+        break;
+      }
+    }
+    if (found.isEmpty())
+    {
+      return null;
+    }
+
+    /*
+     * 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)
+      {
+        if (type.equals(sf.getType()))
+        {
+          result.add(sf);
+          added++;
+        }
+        if (added == toAdd)
+        {
+          break;
+        }
+      }
+    }
+
+    return new MappedFeatures(mapping.getMapping(), mapFrom, pos, residue,
+            result);
+  }
+
+  @Override
   public boolean isVisible(SequenceFeature feature)
   {
     if (feature == null)