JAL-2792 JAL-3187 linked features (if shown) in Feature details submenu
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 17 Jun 2019 15:59:08 +0000 (16:59 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 17 Jun 2019 15:59:08 +0000 (16:59 +0100)
src/jalview/datamodel/MappedFeatures.java
src/jalview/datamodel/SequenceFeature.java
src/jalview/gui/IdPanel.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/SeqPanel.java
test/jalview/datamodel/SequenceFeatureTest.java
test/jalview/gui/AlignFrameTest.java
test/jalview/gui/PopupMenuTest.java
test/jalview/project/Jalview2xmlTests.java

index 4c63916..2f90a7c 100644 (file)
@@ -17,27 +17,27 @@ import java.util.List;
 public class MappedFeatures
 {
   /*
-   * the mapping from CDS to peptide
+   * the mapping from one sequence to another
    */
   public final Mapping mapping;
 
   /**
-   * the CDS sequence mapped to
+   * the sequence mapped to
    */
   public final SequenceI fromSeq;
 
   /*
-   * the residue position in the peptide sequence
+   * the residue position in the sequence mapped from
    */
   public final int fromPosition;
 
   /*
-   * the peptide residue at the position 
+   * the residue at fromPosition 
    */
   public final char fromResidue;
 
   /*
-   * features on CDS that overlap the codon positions
+   * features on the sequence mapped to that overlap the mapped positions
    */
   public final List<SequenceFeature> features;
 
index e7e1342..c8a7def 100755 (executable)
@@ -607,11 +607,11 @@ public class SequenceFeature implements FeatureLocationI
   /**
    * Answers an html-formatted report of feature details
    * 
-   * @param sequence
+   * @param seqName
    * 
    * @return
    */
-  public String getDetailsReport(SequenceI sequence)
+  public String getDetailsReport(String seqName)
   {
     FeatureSourceI metadata = FeatureSources.getInstance()
             .getSource(source);
@@ -619,7 +619,7 @@ public class SequenceFeature implements FeatureLocationI
     StringBuilder sb = new StringBuilder(128);
     sb.append("<br>");
     sb.append("<table>");
-    sb.append(String.format(ROW_DATA, "Location", sequence.getName(),
+    sb.append(String.format(ROW_DATA, "Location", seqName,
             begin == end ? begin
                     : begin + (isContactFeature() ? ":" : "-") + end));
     sb.append(String.format(ROW_DATA, "Type", type, ""));
index d11d7a1..2b1507a 100755 (executable)
@@ -22,7 +22,6 @@ package jalview.gui;
 
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Sequence;
-import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.gui.SeqPanel.MousePos;
@@ -369,28 +368,12 @@ public class IdPanel extends JPanel
     }
 
     Sequence sq = (Sequence) av.getAlignment().getSequenceAt(pos.seqIndex);
-
-    /*
-     *  build a new links menu based on the current links
-     *  and any non-positional features
-     */
-    List<SequenceFeature> features = null;
     if (sq != null)
     {
-    List<String> nlinks = Preferences.sequenceUrlLinks.getLinksForMenu();
-      features = sq.getFeatures().getNonPositionalFeatures();
-    for (SequenceFeature sf : features)
-    {
-      if (sf.links != null)
-      {
-        nlinks.addAll(sf.links);
-      }
-    }
+      PopupMenu pop = new PopupMenu(alignPanel, sq,
+              Preferences.getGroupURLLinks());
+      pop.show(this, e.getX(), e.getY());
     }
-
-    PopupMenu pop = new PopupMenu(alignPanel, sq, features,
-            Preferences.getGroupURLLinks());
-    pop.show(this, e.getX(), e.getY());
   }
 
   /**
index 06e35cd..415054e 100644 (file)
@@ -24,6 +24,7 @@ import jalview.analysis.AAFrequency;
 import jalview.analysis.AlignmentAnnotationUtils;
 import jalview.analysis.AlignmentUtils;
 import jalview.analysis.Conservation;
+import jalview.api.AlignViewportI;
 import jalview.bin.Cache;
 import jalview.commands.ChangeCaseCommand;
 import jalview.commands.EditCommand;
@@ -32,6 +33,7 @@ import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.DBRefEntry;
 import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.MappedFeatures;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
@@ -46,11 +48,13 @@ import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ColourSchemes;
 import jalview.schemes.PIDColourScheme;
 import jalview.schemes.ResidueColourScheme;
+import jalview.util.Comparison;
 import jalview.util.GroupUrlLink;
 import jalview.util.GroupUrlLink.UrlStringTooLongException;
 import jalview.util.MessageManager;
 import jalview.util.StringUtils;
 import jalview.util.UrlLink;
+import jalview.viewmodel.seqfeatures.FeatureRendererModel;
 
 import java.awt.Color;
 import java.awt.event.ActionEvent;
@@ -64,6 +68,7 @@ import java.util.Hashtable;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.Vector;
@@ -344,29 +349,33 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
    * Constructor for a PopupMenu for a click in the alignment panel (on a residue)
    * 
    * @param ap
+   *              the panel in which the mouse is clicked
    * @param seq
-   * @param features
-   *                   sequence features overlapping the clicked residue
+   *              the sequence under the mouse
+   * @throws NullPointerException
+   *                                if seq is null
    */
-  public PopupMenu(final AlignmentPanel ap, SequenceI seq,
-          List<SequenceFeature> features)
+  public PopupMenu(final AlignmentPanel ap, SequenceI seq, int column)
   {
-    this(false, ap, seq, features, null);
+    this(false, ap, seq, column, null);
   }
 
   /**
    * Constructor for a PopupMenu for a click in the sequence id panel
    * 
    * @param alignPanel
+   *                     the panel in which the mouse is clicked
    * @param seq
-   * @param features
-   *                     non-positional features for the sequence
+   *                     the sequence under the mouse click
    * @param groupLinks
+   *                     templates for sequence external links
+   * @throws NullPointerException
+   *                                if seq is null
    */
   public PopupMenu(final AlignmentPanel alignPanel, final SequenceI seq,
-          List<SequenceFeature> features, List<String> groupLinks)
+          List<String> groupLinks)
   {
-    this(true, alignPanel, seq, features, groupLinks);
+    this(true, alignPanel, seq, -1, groupLinks);
   }
 
   /**
@@ -376,14 +385,15 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
    * @param fromIdPanel
    * @param alignPanel
    * @param seq
-   * @param features
+   * @param column
+   *                      aligned column position (0...)
    * @param groupLinks
    */
   private PopupMenu(boolean fromIdPanel,
           final AlignmentPanel alignPanel,
-          final SequenceI seq, List<SequenceFeature> features,
-          List<String> groupLinks)
+          final SequenceI seq, final int column, List<String> groupLinks)
   {
+    Objects.requireNonNull(seq);
     this.forIdPanel = fromIdPanel;
     this.ap = alignPanel;
     sequence = seq;
@@ -409,7 +419,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
      * 'reference annotations' that may be added to the alignment. First for the
      * currently selected sequence (if there is one):
      */
-    final List<SequenceI> selectedSequence = (forIdPanel
+    final List<SequenceI> selectedSequence = (forIdPanel && seq != null
             ? Arrays.asList(seq)
             : Collections.<SequenceI> emptyList());
     buildAnnotationTypesMenus(seqShowAnnotationsMenu,
@@ -701,11 +711,41 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
       rnaStructureMenu.setVisible(false);
     }
 
+    addLinksAndFeatures(seq, column);
+  }
+
+  /**
+   * Adds
+   * <ul>
+   * <li>configured sequence database links (ID panel popup menu)</li>
+   * <li>non-positional feature links (ID panel popup menu)</li>
+   * <li>positional feature links (alignment panel popup menu)</li>
+   * <li>feature details links (alignment panel popup menu)</li>
+   * </ul>
+   * If this panel is also showed complementary (CDS/protein) features, then links
+   * to their feature details are also added.
+   * 
+   * @param seq
+   * @param column
+   */
+  void addLinksAndFeatures(final SequenceI seq, final int column)
+  {
+    List<SequenceFeature> features = null;
+    if (forIdPanel)
+    {
+      features = sequence.getFeatures().getNonPositionalFeatures();
+    }
+    else
+    {
+      features = ap.getFeatureRenderer().findFeaturesAtColumn(sequence,
+              column + 1);
+    }
+
     addLinks(seq, features);
 
     if (!forIdPanel)
     {
-      addFeatureDetails(features);
+      addFeatureDetails(features, seq, column);
     }
   }
 
@@ -713,75 +753,127 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
    * Add a link to show feature details for each sequence feature
    * 
    * @param features
+   * @param column
+   * @param seq
    */
-  protected void addFeatureDetails(List<SequenceFeature> features)
+  protected void addFeatureDetails(List<SequenceFeature> features,
+          SequenceI seq, int column)
   {
-    if (features == null || features.isEmpty())
+    /*
+     * add features in CDS/protein complement at the corresponding
+     * position if configured to do so
+     */
+    MappedFeatures mf = null;
+    if (ap.av.isShowComplementFeatures())
+    {
+      if (!Comparison.isGap(sequence.getCharAt(column)))
+      {
+        AlignViewportI complement = ap.getAlignViewport()
+                .getCodingComplement();
+        AlignFrame af = Desktop.getAlignFrameFor(complement);
+        FeatureRendererModel fr2 = af.getFeatureRenderer();
+        int seqPos = sequence.findPosition(column);
+        mf = fr2.findComplementFeaturesAtResidue(sequence, seqPos);
+      }
+    }
+
+    if (features.isEmpty() && mf == null)
     {
+      /*
+       * no features to show at this position
+       */
       return;
     }
+
     JMenu details = new JMenu(
             MessageManager.getString("label.feature_details"));
     add(details);
 
+    String name = seq.getName();
     for (final SequenceFeature sf : features)
     {
-      int start = sf.getBegin();
-      int end = sf.getEnd();
-      String desc = null;
-      if (start == end)
+      addFeatureDetailsMenuItem(details, name, sf);
+    }
+
+    if (mf != null)
+    {
+      name = mf.fromSeq == seq ? mf.mapping.getTo().getName()
+              : mf.fromSeq.getName();
+      for (final SequenceFeature sf : mf.features)
       {
-        desc = String.format("%s %d", sf.getType(), start);
+        addFeatureDetailsMenuItem(details, name, sf);
       }
-      else
+    }
+  }
+
+  /**
+   * A helper method to add one menu item whose action is to show details for one
+   * feature
+   * 
+   * @param details
+   * @param seqName
+   * @param sf
+   */
+  void addFeatureDetailsMenuItem(JMenu details, final String seqName,
+          final SequenceFeature sf)
+  {
+    int start = sf.getBegin();
+    int end = sf.getEnd();
+    String desc = null;
+    if (start == end)
+    {
+      desc = String.format("%s %d", sf.getType(), start);
+    }
+    else
+    {
+      desc = String.format("%s %d-%d", sf.getType(), start, end);
+    }
+    String tooltip = desc;
+    String description = sf.getDescription();
+    if (description != null)
+    {
+      description = StringUtils.stripHtmlTags(description);
+      if (description.length() > 12)
       {
-        desc = String.format("%s %d-%d", sf.getType(), start, end);
+        desc = desc + " " + description.substring(0, 12) + "..";
       }
-      String tooltip = desc;
-      String description = sf.getDescription();
-      if (description != null)
+      else
       {
-        description = StringUtils.stripHtmlTags(description);
-        if (description.length() > 12)
-        {
-          desc = desc + " " + description.substring(0, 12) + "..";
-        }
-        else
-        {
-          desc = desc + " " + description;
-        }
-        tooltip = tooltip + " " + description;
+        desc = desc + " " + description;
       }
-      if (sf.getFeatureGroup() != null)
+      tooltip = tooltip + " " + description;
+    }
+    if (sf.getFeatureGroup() != null)
+    {
+      tooltip = tooltip + (" (" + sf.getFeatureGroup() + ")");
+    }
+    JMenuItem item = new JMenuItem(desc);
+    item.setToolTipText(tooltip);
+    item.addActionListener(new ActionListener()
+    {
+      @Override
+      public void actionPerformed(ActionEvent e)
       {
-        tooltip = tooltip + (" (" + sf.getFeatureGroup() + ")");
+        showFeatureDetails(seqName, sf);
       }
-      JMenuItem item = new JMenuItem(desc);
-      item.setToolTipText(tooltip);
-      item.addActionListener(new ActionListener()
-      {
-        @Override
-        public void actionPerformed(ActionEvent e)
-        {
-          showFeatureDetails(sf);
-        }
-      });
-      details.add(item);
-    }
+    });
+    details.add(item);
   }
 
   /**
    * Opens a panel showing a text report of feature dteails
    * 
+   * @param seqName
+   * 
    * @param sf
    */
-  protected void showFeatureDetails(SequenceFeature sf)
+  protected void showFeatureDetails(String seqName, SequenceFeature sf)
   {
     CutAndPasteHtmlTransfer cap = new CutAndPasteHtmlTransfer();
     // it appears Java's CSS does not support border-collapse :-(
     cap.addStylesheetRule("table { border-collapse: collapse;}");
     cap.addStylesheetRule("table, td, th {border: 1px solid black;}");
-    cap.setText(sf.getDetailsReport(sequence));
+    cap.setText(sf.getDetailsReport(seqName));
 
     Desktop.addInternalFrame(cap,
             MessageManager.getString("label.feature_details"), 500, 500);
index e7bd200..14c3818 100644 (file)
@@ -2254,11 +2254,11 @@ public class SeqPanel extends JPanel
     final int column = pos.column;
     final int seq = pos.seqIndex;
     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
-    List<SequenceFeature> features = ap.getFeatureRenderer()
-            .findFeaturesAtColumn(sequence, column + 1);
-
-    PopupMenu pop = new PopupMenu(ap, sequence, features);
-    pop.show(this, evt.getX(), evt.getY());
+    if (sequence != null)
+    {
+      PopupMenu pop = new PopupMenu(ap, sequence, column);
+      pop.show(this, evt.getX(), evt.getY());
+    }
   }
 
   /**
index 2774a16..cd8f9eb 100644 (file)
@@ -278,13 +278,14 @@ public class SequenceFeatureTest
   public void testGetDetailsReport()
   {
     SequenceI seq = new Sequence("TestSeq", "PLRFQMD");
+    String seqName = seq.getName();
 
     // single locus, no group, no score
     SequenceFeature sf = new SequenceFeature("variant", "G,C", 22, 22, null);
     String expected = "<br><table><tr><td>Location</td><td>TestSeq</td><td>22</td></tr>"
             + "<tr><td>Type</td><td>variant</td><td></td></tr>"
             + "<tr><td>Description</td><td>G,C</td><td></td></tr></table>";
-    assertEquals(expected, sf.getDetailsReport(seq));
+    assertEquals(expected, sf.getDetailsReport(seqName));
 
     // contact feature
     sf = new SequenceFeature("Disulphide Bond", "a description", 28, 31,
@@ -292,7 +293,7 @@ public class SequenceFeatureTest
     expected = "<br><table><tr><td>Location</td><td>TestSeq</td><td>28:31</td></tr>"
             + "<tr><td>Type</td><td>Disulphide Bond</td><td></td></tr>"
             + "<tr><td>Description</td><td>a description</td><td></td></tr></table>";
-    assertEquals(expected, sf.getDetailsReport(seq));
+    assertEquals(expected, sf.getDetailsReport(seqName));
 
     sf = new SequenceFeature("variant", "G,C", 22, 33,
             12.5f, "group");
@@ -305,7 +306,7 @@ public class SequenceFeatureTest
             + "<tr><td>Group</td><td>group</td><td></td></tr>"
             + "<tr><td>Child</td><td></td><td>ENSP002</td></tr>"
             + "<tr><td>Parent</td><td></td><td>ENSG001</td></tr></table>";
-    assertEquals(expected, sf.getDetailsReport(seq));
+    assertEquals(expected, sf.getDetailsReport(seqName));
 
     /*
      * feature with embedded html link in description
@@ -316,6 +317,6 @@ public class SequenceFeatureTest
             + "<tr><td>Type</td><td>Pfam</td><td></td></tr>"
             + "<tr><td>Description</td><td>Fer2 Status: True Positive <a href=\"http://pfam.xfam.org/family/PF00111\">Pfam 8_8</a></td><td></td></tr>"
             + "<tr><td>Group</td><td>Uniprot</td><td></td></tr></table>";
-    assertEquals(expected, sf.getDetailsReport(seq));
+    assertEquals(expected, sf.getDetailsReport(seqName));
   }
 }
index 454ff61..11a5f43 100644 (file)
@@ -261,8 +261,11 @@ public class AlignFrameTest
 
     /*
      * apply 30% Conservation to group
+     * (notice menu action applies to selection group even if mouse click
+     * is at a sequence not in the group)
      */
-    PopupMenu popupMenu = new PopupMenu(af.alignPanel, null, null);
+    PopupMenu popupMenu = new PopupMenu(af.alignPanel, al.getSequenceAt(2),
+            null);
     popupMenu.changeColour_actionPerformed(JalviewColourScheme.Strand
             .toString());
     assertTrue(sg.getColourScheme() instanceof StrandColourScheme);
@@ -540,7 +543,8 @@ public class AlignFrameTest
     sg.setStartRes(15);
     sg.setEndRes(25);
     av.setSelectionGroup(sg);
-    PopupMenu popupMenu = new PopupMenu(af.alignPanel, null, null);
+    PopupMenu popupMenu = new PopupMenu(af.alignPanel, al.getSequenceAt(0),
+            null);
     popupMenu.changeColour_actionPerformed(JalviewColourScheme.Strand
             .toString());
     assertTrue(sg.getColourScheme() instanceof StrandColourScheme);
index df86494..bf961d8 100644 (file)
@@ -115,7 +115,7 @@ public class PopupMenuTest
             DataSourceType.PASTE, FileFormat.Fasta);
     AlignFrame af = new AlignFrame(alignment, 700, 500);
     parentPanel = new AlignmentPanel(af, af.getViewport());
-    testee = new PopupMenu(parentPanel, null, null);
+    testee = new PopupMenu(parentPanel, alignment.getSequenceAt(0), null);
     int i = 0;
     for (SequenceI seq : alignment.getSequences())
     {
index 521ef81..5182ad4 100644 (file)
@@ -842,13 +842,16 @@ public class Jalview2xmlTests extends Jalview2xmlBase
     /*
      * create a group with Strand colouring, 30% Conservation
      * and 40% PID threshold
+     * (notice menu action applies to selection group even if mouse click
+     * is at a sequence not in the group)
      */
     SequenceGroup sg = new SequenceGroup();
     sg.addSequence(al.getSequenceAt(0), false);
     sg.setStartRes(15);
     sg.setEndRes(25);
     av.setSelectionGroup(sg);
-    PopupMenu popupMenu = new PopupMenu(af.alignPanel, null, null);
+    PopupMenu popupMenu = new PopupMenu(af.alignPanel, al.getSequenceAt(2),
+            null);
     popupMenu.changeColour_actionPerformed(
             JalviewColourScheme.Strand.toString());
     assertTrue(sg.getColourScheme() instanceof StrandColourScheme);