JAL-2547 tooltip/AmendFeatures on gap straddled by feature, not for
[jalview.git] / src / jalview / appletgui / SeqPanel.java
index c413a06..f0ec51c 100644 (file)
@@ -39,6 +39,7 @@ import jalview.structure.SelectionSource;
 import jalview.structure.SequenceListener;
 import jalview.structure.StructureSelectionManager;
 import jalview.structure.VamsasSource;
+import jalview.util.Comparison;
 import jalview.util.MappingUtils;
 import jalview.util.MessageManager;
 import jalview.viewmodel.AlignmentViewport;
@@ -53,6 +54,10 @@ import java.awt.event.InputEvent;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
 import java.util.Vector;
 
 public class SeqPanel extends Panel implements MouseMotionListener,
@@ -227,7 +232,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     endEditing();
     if (av.getWrapAlignment())
     {
-      ap.scrollToWrappedVisible(seqCanvas.cursorX);
+      av.getRanges().scrollToWrappedVisible(seqCanvas.cursorX);
     }
     else
     {
@@ -235,17 +240,17 @@ public class SeqPanel extends Panel implements MouseMotionListener,
       HiddenColumns hidden = av.getAlignment().getHiddenColumns();
       while (seqCanvas.cursorY < ranges.getStartSeq())
       {
-        ap.scrollUp(true);
+        ranges.scrollUp(true);
       }
-      while (seqCanvas.cursorY + 1 > ranges.getEndSeq())
+      while (seqCanvas.cursorY > ranges.getEndSeq())
       {
-        ap.scrollUp(false);
+        ranges.scrollUp(false);
       }
       while (seqCanvas.cursorX < hidden.adjustForHiddenColumns(ranges
               .getStartRes()))
       {
 
-        if (!ap.scrollRight(false))
+        if (!ranges.scrollRight(false))
         {
           break;
         }
@@ -253,7 +258,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
       while (seqCanvas.cursorX > hidden.adjustForHiddenColumns(ranges
               .getEndRes()))
       {
-        if (!ap.scrollRight(true))
+        if (!ranges.scrollRight(true))
         {
           break;
         }
@@ -412,13 +417,13 @@ public class SeqPanel extends Panel implements MouseMotionListener,
    * 
    * @param sequence
    *          aligned sequence object
-   * @param res
+   * @param column
    *          alignment column
    * @param seq
    *          index of sequence in alignment
-   * @return position of res in sequence
+   * @return position of column in sequence or -1 if at gap
    */
-  void setStatusMessage(SequenceI sequence, int res, int seq)
+  void setStatusMessage(SequenceI sequence, int column, int seq)
   {
     // TODO remove duplication of identical gui method
     StringBuilder text = new StringBuilder(32);
@@ -429,7 +434,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     /*
      * Try to translate the display character to residue name (null for gap).
      */
-    final String displayChar = String.valueOf(sequence.getCharAt(res));
+    final String displayChar = String.valueOf(sequence.getCharAt(column));
     if (av.getAlignment().isNucleotide())
     {
       residue = ResidueProperties.nucleotideName.get(displayChar);
@@ -452,7 +457,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     int pos = -1;
     if (residue != null)
     {
-      pos = sequence.findPosition(res);
+      pos = sequence.findPosition(column);
       text.append(" (").append(Integer.toString(pos)).append(")");
     }
 
@@ -562,20 +567,23 @@ public class SeqPanel extends Panel implements MouseMotionListener,
         av.setSelectionGroup(null);
       }
 
-      SequenceFeature[] features = findFeaturesAtRes(sequence,
-              sequence.findPosition(findRes(evt)));
+      int column = findRes(evt);
+      boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
+      List<SequenceFeature> features = findFeaturesAtRes(sequence,
+              sequence.findPosition(column));
+      if (isGapped)
+      {
+        removeAdjacentFeatures(features, column + 1, sequence);
+      }
 
-      if (features != null && features.length > 0)
+      if (!features.isEmpty())
       {
         SearchResultsI highlight = new SearchResults();
-        highlight.addResult(sequence, features[0].getBegin(),
-                features[0].getEnd());
+        highlight.addResult(sequence, features.get(0).getBegin(), features
+                .get(0).getEnd());
         seqCanvas.highlightSearchResults(highlight);
-      }
-      if (features != null && features.length > 0)
-      {
         seqCanvas.getFeatureRenderer().amendFeatures(
-                new SequenceI[] { sequence }, features, false, ap, null);
+                Collections.singletonList(sequence), features, false, ap);
 
         seqCanvas.highlightSearchResults(null);
       }
@@ -585,13 +593,13 @@ public class SeqPanel extends Panel implements MouseMotionListener,
   @Override
   public void mouseReleased(MouseEvent evt)
   {
+    boolean didDrag = mouseDragging; // did we come here after a drag
     mouseDragging = false;
     mouseWheelPressed = false;
-    ap.paintAlignment(true);
 
     if (!editingSeqs)
     {
-      doMouseReleasedDefineMode(evt);
+      doMouseReleasedDefineMode(evt, didDrag);
       return;
     }
 
@@ -746,10 +754,16 @@ public class SeqPanel extends Panel implements MouseMotionListener,
   {
     if (av.isFollowHighlight())
     {
+      // don't allow highlight of protein/cDNA to also scroll a complementary
+      // panel,as this sets up a feedback loop (scrolling panel 1 causes moused
+      // over residue to change abruptly, causing highlighted residue in panel 2
+      // to change, causing a scroll in panel 1 etc)
+      ap.setToScrollComplementPanel(false);
       if (ap.scrollToPosition(results, true))
       {
         ap.alignFrame.repaint();
       }
+      ap.setToScrollComplementPanel(true);
     }
     setStatusMessage(results);
     seqCanvas.highlightSearchResults(results);
@@ -772,10 +786,10 @@ public class SeqPanel extends Panel implements MouseMotionListener,
   @Override
   public void mouseMoved(MouseEvent evt)
   {
-    int res = findRes(evt);
+    final int column = findRes(evt);
     int seq = findSeq(evt);
 
-    if (seq >= av.getAlignment().getHeight() || seq < 0 || res < 0)
+    if (seq >= av.getAlignment().getHeight() || seq < 0 || column < 0)
     {
       if (tooltip != null)
       {
@@ -785,7 +799,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     }
 
     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
-    if (res > sequence.getLength())
+    if (column > sequence.getLength())
     {
       if (tooltip != null)
       {
@@ -794,38 +808,34 @@ public class SeqPanel extends Panel implements MouseMotionListener,
       return;
     }
 
-    int respos = sequence.findPosition(res);
-    if (ssm != null)
+    final char ch = sequence.getCharAt(column);
+    boolean isGapped = Comparison.isGap(ch);
+    // find residue at column (or nearest if at a gap)
+    int respos = sequence.findPosition(column);
+
+    if (ssm != null && !isGapped)
     {
-      mouseOverSequence(sequence, res, respos);
+      mouseOverSequence(sequence, column, respos);
     }
 
     StringBuilder text = new StringBuilder();
     text.append("Sequence ").append(Integer.toString(seq + 1))
             .append(" ID: ").append(sequence.getName());
 
-    String obj = null;
-    final String ch = String.valueOf(sequence.getCharAt(res));
-    if (av.getAlignment().isNucleotide())
+    if (!isGapped)
     {
-      obj = ResidueProperties.nucleotideName.get(ch);
-      if (obj != null)
+      if (av.getAlignment().isNucleotide())
       {
-        text.append(" Nucleotide: ").append(obj);
+        String base = ResidueProperties.nucleotideName.get(ch);
+        text.append(" Nucleotide: ").append(base == null ? ch : base);
       }
-    }
-    else
-    {
-      obj = "X".equalsIgnoreCase(ch) ? "X" : ResidueProperties.aa2Triplet
-              .get(ch);
-      if (obj != null)
+      else
       {
-        text.append(" Residue: ").append(obj);
+        String residue = (ch == 'x' || ch == 'X') ? "X"
+                : ResidueProperties.aa2Triplet
+                .get(String.valueOf(ch));
+        text.append(" Residue: ").append(residue == null ? ch : residue);
       }
-    }
-
-    if (obj != null)
-    {
       text.append(" (").append(Integer.toString(respos)).append(")");
     }
 
@@ -837,7 +847,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     {
       for (int g = 0; g < groups.length; g++)
       {
-        if (groups[g].getStartRes() <= res && groups[g].getEndRes() >= res)
+        if (groups[g].getStartRes() <= column && groups[g].getEndRes() >= column)
         {
           if (!groups[g].getName().startsWith("JTreeGroup")
                   && !groups[g].getName().startsWith("JGroup"))
@@ -853,33 +863,37 @@ public class SeqPanel extends Panel implements MouseMotionListener,
       }
     }
 
-    // use aa to see if the mouse pointer is on a
-    SequenceFeature[] allFeatures = findFeaturesAtRes(sequence,
-            sequence.findPosition(res));
-
-    int index = 0;
-    while (index < allFeatures.length)
+    /*
+     * add feature details to tooltip, including any that straddle
+     * a gapped position
+     */
+    if (av.isShowSequenceFeatures())
     {
-      SequenceFeature sf = allFeatures[index];
-
-      tooltipText.append(sf.getType() + " " + sf.begin + ":" + sf.end);
-
-      if (sf.getDescription() != null)
+      List<SequenceFeature> allFeatures = findFeaturesAtRes(sequence,
+              sequence.findPosition(column));
+      if (isGapped)
       {
-        tooltipText.append(" " + sf.getDescription());
+        removeAdjacentFeatures(allFeatures, column + 1, sequence);
       }
-
-      if (sf.getValue("status") != null)
+      for (SequenceFeature sf : allFeatures)
       {
-        String status = sf.getValue("status").toString();
-        if (status.length() > 0)
+        tooltipText.append(sf.getType() + " " + sf.begin + ":" + sf.end);
+
+        if (sf.getDescription() != null)
         {
-          tooltipText.append(" (" + sf.getValue("status") + ")");
+          tooltipText.append(" " + sf.getDescription());
         }
-      }
-      tooltipText.append("\n");
 
-      index++;
+        if (sf.getValue("status") != null)
+        {
+          String status = sf.getValue("status").toString();
+          if (status.length() > 0)
+          {
+            tooltipText.append(" (" + sf.getValue("status") + ")");
+          }
+        }
+        tooltipText.append("\n");
+      }
     }
 
     if (tooltip == null)
@@ -892,9 +906,35 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     }
   }
 
-  SequenceFeature[] findFeaturesAtRes(SequenceI sequence, int res)
+  /**
+   * Removes from the list of features any that start after, or end before, the
+   * given column position. This allows us to retain only those features
+   * adjacent to a gapped position that straddle the position.
+   * 
+   * @param features
+   * @param column
+   *          alignment column (1..)
+   * @param sequence
+   */
+  protected void removeAdjacentFeatures(List<SequenceFeature> features,
+          int column, SequenceI sequence)
   {
-    Vector tmp = new Vector();
+    // TODO should this be an AlignViewController method (shared by gui)?
+    ListIterator<SequenceFeature> it = features.listIterator();
+    while (it.hasNext())
+    {
+      SequenceFeature sf = it.next();
+      if (sequence.findIndex(sf.getBegin()) > column
+              || sequence.findIndex(sf.getEnd()) < column)
+      {
+        it.remove();
+      }
+    }
+  }
+
+  List<SequenceFeature> findFeaturesAtRes(SequenceI sequence, int res)
+  {
+    List<SequenceFeature> result = new ArrayList<SequenceFeature>();
     SequenceFeature[] features = sequence.getSequenceFeatures();
     if (features != null)
     {
@@ -917,15 +957,12 @@ public class SeqPanel extends Panel implements MouseMotionListener,
         if ((features[i].getBegin() <= res)
                 && (features[i].getEnd() >= res))
         {
-          tmp.addElement(features[i]);
+          result.add(features[i]);
         }
       }
     }
 
-    features = new SequenceFeature[tmp.size()];
-    tmp.copyInto(features);
-
-    return features;
+    return result;
   }
 
   Tooltip tooltip;
@@ -965,7 +1002,9 @@ public class SeqPanel extends Panel implements MouseMotionListener,
           fontSize = 1;
         }
 
-        av.setFont(new Font(av.font.getName(), av.font.getStyle(), fontSize));
+        av.setFont(
+                new Font(av.font.getName(), av.font.getStyle(), fontSize),
+                true);
         av.setCharWidth(oldWidth);
       }
       else
@@ -1449,24 +1488,21 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     // DETECT RIGHT MOUSE BUTTON IN AWT
     if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
     {
-      SequenceFeature[] allFeatures = findFeaturesAtRes(sequence,
+      List<SequenceFeature> allFeatures = findFeaturesAtRes(sequence,
               sequence.findPosition(res));
 
       Vector<String> links = null;
-      if (allFeatures != null)
+      for (SequenceFeature sf : allFeatures)
       {
-        for (int i = 0; i < allFeatures.length; i++)
+        if (sf.links != null)
         {
-          if (allFeatures[i].links != null)
+          if (links == null)
           {
-            if (links == null)
-            {
-              links = new Vector<String>();
-            }
-            for (int j = 0; j < allFeatures[i].links.size(); j++)
-            {
-              links.addElement(allFeatures[i].links.elementAt(j));
-            }
+            links = new Vector<String>();
+          }
+          for (int j = 0; j < sf.links.size(); j++)
+          {
+            links.addElement(sf.links.elementAt(j));
           }
         }
       }
@@ -1510,7 +1546,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     }
   }
 
-  public void doMouseReleasedDefineMode(MouseEvent evt)
+  public void doMouseReleasedDefineMode(MouseEvent evt, boolean afterDrag)
   {
     if (stretchGroup == null)
     {
@@ -1520,7 +1556,8 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     // but defer colourscheme update until hidden sequences are passed in
     boolean vischange = stretchGroup.recalcConservation(true);
     // here we rely on stretchGroup == av.getSelection()
-    needOverviewUpdate |= vischange && av.isSelectionDefinedGroup();
+    needOverviewUpdate |= vischange && av.isSelectionDefinedGroup()
+            && afterDrag;
     if (stretchGroup.cs != null)
     {
       stretchGroup.cs.alignmentChanged(stretchGroup,
@@ -1759,24 +1796,24 @@ public class SeqPanel extends Panel implements MouseMotionListener,
           if (mouseDragging && evt.getY() < 0
                   && av.getRanges().getStartSeq() > 0)
           {
-            running = ap.scrollUp(true);
+            running = av.getRanges().scrollUp(true);
           }
 
           if (mouseDragging && evt.getY() >= getSize().height
                   && av.getAlignment().getHeight() > av.getRanges()
                           .getEndSeq())
           {
-            running = ap.scrollUp(false);
+            running = av.getRanges().scrollUp(false);
           }
 
           if (mouseDragging && evt.getX() < 0)
           {
-            running = ap.scrollRight(false);
+            running = av.getRanges().scrollRight(false);
           }
 
           else if (mouseDragging && evt.getX() >= getSize().width)
           {
-            running = ap.scrollRight(true);
+            running = av.getRanges().scrollRight(true);
           }
         }