Merge branch 'master' into portforward/JAL-2675_2102b1to2103
[jalview.git] / src / jalview / gui / SeqPanel.java
index 056a602..e99e577 100644 (file)
@@ -62,7 +62,6 @@ import java.awt.event.MouseWheelListener;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.ListIterator;
 
 import javax.swing.JPanel;
 import javax.swing.SwingUtilities;
@@ -85,6 +84,16 @@ public class SeqPanel extends JPanel
   /** DOCUMENT ME!! */
   public AlignmentPanel ap;
 
+  /*
+   * last column position for mouseMoved event
+   */
+  private int lastMouseColumn;
+
+  /*
+   * last sequence offset for mouseMoved event
+   */
+  private int lastMouseSeq;
+
   protected int lastres;
 
   protected int startseq;
@@ -171,6 +180,9 @@ public class SeqPanel extends JPanel
       ssm.addStructureViewerListener(this);
       ssm.addSelectionListener(this);
     }
+
+    lastMouseColumn = -1;
+    lastMouseSeq = -1;
   }
 
   int startWrapBlock = -1;
@@ -204,7 +216,7 @@ public class SeqPanel extends JPanel
 
       int y = evt.getY();
       y -= hgap;
-      x = Math.max(0, x - seqCanvas.LABEL_WEST);
+      x = Math.max(0, x - seqCanvas.labelWidthWest);
 
       int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth());
       if (cwidth < 1)
@@ -669,6 +681,8 @@ public class SeqPanel extends JPanel
     }
     lastSearchResults = results;
 
+    boolean wasScrolled = false;
+
     if (av.isFollowHighlight())
     {
       // don't allow highlight of protein/cDNA to also scroll a complementary
@@ -676,14 +690,19 @@ public class SeqPanel extends JPanel
       // 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, false))
+      wasScrolled = ap.scrollToPosition(results, false);
+      if (wasScrolled)
       {
         seqCanvas.revalidate();
       }
       ap.setToScrollComplementPanel(true);
     }
-    setStatusMessage(results);
-    seqCanvas.highlightSearchResults(results);
+
+    boolean noFastPaint = wasScrolled && av.getWrapAlignment();
+    if (seqCanvas.highlightSearchResults(results, noFastPaint))
+    {
+      setStatusMessage(results);
+    }
   }
 
   @Override
@@ -719,8 +738,18 @@ public class SeqPanel extends JPanel
     int seq = findSeq(evt);
     if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight())
     {
+      lastMouseSeq = -1;
+      return;
+    }
+    if (column == lastMouseColumn && seq == lastMouseSeq)
+    {
+      /*
+       * just a pixel move without change of residue
+       */
       return;
     }
+    lastMouseColumn = column;
+    lastMouseSeq = seq;
 
     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
 
@@ -771,11 +800,7 @@ public class SeqPanel extends JPanel
     if (av.isShowSequenceFeatures())
     {
       List<SequenceFeature> features = ap.getFeatureRenderer()
-              .findFeaturesAtRes(sequence.getDatasetSequence(), pos);
-      if (isGapped)
-      {
-        removeAdjacentFeatures(features, column + 1, sequence);
-      }
+              .findFeaturesAtColumn(sequence, column + 1);
       seqARep.appendFeatures(tooltipText, pos, features,
               this.ap.getSeqPanel().seqCanvas.fr.getMinMax());
     }
@@ -786,45 +811,13 @@ public class SeqPanel extends JPanel
     }
     else
     {
-      if (lastTooltip == null
-              || !lastTooltip.equals(tooltipText.toString()))
+      String textString = tooltipText.toString();
+      if (lastTooltip == null || !lastTooltip.equals(textString))
       {
-        String formatedTooltipText = JvSwingUtils.wrapTooltip(true,
-                tooltipText.toString());
-        // String formatedTooltipText = tooltipText.toString();
-        setToolTipText(formatedTooltipText);
-        lastTooltip = tooltipText.toString();
-      }
-
-    }
-
-  }
-
-  /**
-   * 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. Contact features
-   * that 'straddle' the position are also removed, since they are not 'at' the
-   * position.
-   * 
-   * @param features
-   * @param column
-   *          alignment column (1..)
-   * @param sequence
-   */
-  protected void removeAdjacentFeatures(List<SequenceFeature> features,
-          final int column, SequenceI sequence)
-  {
-    // TODO should this be an AlignViewController method (and reused by applet)?
-    ListIterator<SequenceFeature> it = features.listIterator();
-    while (it.hasNext())
-    {
-      SequenceFeature sf = it.next();
-      if (sf.isContactFeature()
-              || sequence.findIndex(sf.getBegin()) > column
-              || sequence.findIndex(sf.getEnd()) < column)
-      {
-        it.remove();
+        String formattedTooltipText = JvSwingUtils.wrapTooltip(true,
+                textString);
+        setToolTipText(formattedTooltipText);
+        lastTooltip = textString;
       }
     }
   }
@@ -883,19 +876,48 @@ public class SeqPanel extends JPanel
    *          aligned sequence object
    * @param column
    *          alignment column
-   * @param seq
+   * @param seqIndex
    *          index of sequence in alignment
    * @return sequence position of residue at column, or adjacent residue if at a
    *         gap
    */
-  int setStatusMessage(SequenceI sequence, final int column, int seq)
+  int setStatusMessage(SequenceI sequence, final int column, int seqIndex)
+  {
+    char sequenceChar = sequence.getCharAt(column);
+    int pos = sequence.findPosition(column);
+    setStatusMessage(sequence, seqIndex, sequenceChar, pos);
+
+    return pos;
+  }
+
+  /**
+   * Builds the status message for the current cursor location and writes it to
+   * the status bar, for example
+   * 
+   * <pre>
+   * Sequence 3 ID: FER1_SOLLC
+   * Sequence 5 ID: FER1_PEA Residue: THR (4)
+   * Sequence 5 ID: FER1_PEA Residue: B (3)
+   * Sequence 6 ID: O.niloticus.3 Nucleotide: Uracil (2)
+   * </pre>
+   * 
+   * @param sequence
+   * @param seqIndex
+   *          sequence position in the alignment (1..)
+   * @param sequenceChar
+   *          the character under the cursor
+   * @param residuePos
+   *          the sequence residue position (if not over a gap)
+   */
+  protected void setStatusMessage(SequenceI sequence, int seqIndex,
+          char sequenceChar, int residuePos)
   {
     StringBuilder text = new StringBuilder(32);
 
     /*
      * Sequence number (if known), and sequence name.
      */
-    String seqno = seq == -1 ? "" : " " + (seq + 1);
+    String seqno = seqIndex == -1 ? "" : " " + (seqIndex + 1);
     text.append("Sequence").append(seqno).append(" ID: ")
             .append(sequence.getName());
 
@@ -904,13 +926,12 @@ public class SeqPanel extends JPanel
     /*
      * Try to translate the display character to residue name (null for gap).
      */
-    final String displayChar = String.valueOf(sequence.getCharAt(column));
-    boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
-    int pos = sequence.findPosition(column);
+    boolean isGapped = Comparison.isGap(sequenceChar);
 
     if (!isGapped)
     {
       boolean nucleotide = av.getAlignment().isNucleotide();
+      String displayChar = String.valueOf(sequenceChar);
       if (nucleotide)
       {
         residue = ResidueProperties.nucleotideName.get(displayChar);
@@ -924,11 +945,9 @@ public class SeqPanel extends JPanel
       text.append(" ").append(nucleotide ? "Nucleotide" : "Residue")
               .append(": ").append(residue == null ? displayChar : residue);
 
-      text.append(" (").append(Integer.toString(pos)).append(")");
+      text.append(" (").append(Integer.toString(residuePos)).append(")");
     }
     ap.alignFrame.statusBar.setText(text.toString());
-
-    return pos;
   }
 
   /**
@@ -956,12 +975,9 @@ public class SeqPanel extends JPanel
 
       if (seq == ds)
       {
-        /*
-         * Convert position in sequence (base 1) to sequence character array
-         * index (base 0)
-         */
-        int start = m.getStart() - m.getSequence().getStart();
-        setStatusMessage(seq, start, sequenceIndex);
+        int start = m.getStart();
+        setStatusMessage(seq, sequenceIndex, seq.getCharAt(start - 1),
+                start);
         return;
       }
     }
@@ -1095,7 +1111,7 @@ public class SeqPanel extends JPanel
     }
 
     mouseDragging = true;
-    if (scrollThread != null)
+    if ((scrollThread != null) && (scrollThread.isRunning()))
     {
       scrollThread.setEvent(evt);
     }
@@ -1258,7 +1274,7 @@ public class SeqPanel extends JPanel
         // Find the next gap before the end
         // of the visible region boundary
         boolean blank = false;
-        for (fixedRight = fixedRight; fixedRight > lastres; fixedRight--)
+        for (; fixedRight > lastres; fixedRight--)
         {
           blank = true;
 
@@ -1521,9 +1537,9 @@ public class SeqPanel extends JPanel
       oldSeq = 0;
     }
 
-    if (scrollThread != null)
+    if ((scrollThread != null) && (scrollThread.isRunning()))
     {
-      scrollThread.running = false;
+      scrollThread.stopScrolling();
       scrollThread = null;
     }
   }
@@ -1542,7 +1558,7 @@ public class SeqPanel extends JPanel
       return;
     }
 
-    if (mouseDragging)
+    if (mouseDragging && scrollThread == null)
     {
       scrollThread = new ScrollThread();
     }
@@ -1568,19 +1584,13 @@ public class SeqPanel extends JPanel
       }
 
       int column = findColumn(evt);
-      boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
 
       /*
        * find features at the position (if not gapped), or straddling
        * the position (if at a gap)
        */
       List<SequenceFeature> features = seqCanvas.getFeatureRenderer()
-              .findFeaturesAtRes(sequence.getDatasetSequence(),
-                      sequence.findPosition(column));
-      if (isGapped)
-      {
-        removeAdjacentFeatures(features, column, sequence);
-      }
+              .findFeaturesAtColumn(sequence, column + 1);
 
       if (!features.isEmpty())
       {
@@ -1588,9 +1598,9 @@ public class SeqPanel extends JPanel
          * highlight the first feature at the position on the alignment
          */
         SearchResultsI highlight = new SearchResults();
-        highlight.addResult(sequence, features.get(0).getBegin(),
-                features.get(0).getEnd());
-        seqCanvas.highlightSearchResults(highlight);
+        highlight.addResult(sequence, features.get(0).getBegin(), features
+                .get(0).getEnd());
+        seqCanvas.highlightSearchResults(highlight, false);
 
         /*
          * open the Amend Features dialog; clear highlighting afterwards,
@@ -1599,7 +1609,8 @@ public class SeqPanel extends JPanel
         List<SequenceI> seqs = Collections.singletonList(sequence);
         seqCanvas.getFeatureRenderer().amendFeatures(seqs, features, false,
                 ap);
-        seqCanvas.highlightSearchResults(null);
+        av.setSearchResults(null); // clear highlighting
+        seqCanvas.repaint(); // draw new/amended features
       }
     }
   }
@@ -1710,43 +1721,53 @@ public class SeqPanel extends JPanel
 
     if (stretchGroup == null)
     {
-      // Only if left mouse button do we want to change group sizes
+      createStretchGroup(res, sequence);
+    }
 
-      // define a new group here
-      SequenceGroup sg = new SequenceGroup();
-      sg.setStartRes(res);
-      sg.setEndRes(res);
-      sg.addSequence(sequence, false);
-      av.setSelectionGroup(sg);
-      stretchGroup = sg;
+    if (stretchGroup != null)
+    {
+      stretchGroup.addPropertyChangeListener(seqCanvas);
+    }
 
-      if (av.getConservationSelected())
-      {
-        SliderPanel.setConservationSlider(ap, av.getResidueShading(),
-                ap.getViewName());
-      }
+    seqCanvas.repaint();
+  }
 
-      if (av.getAbovePIDThreshold())
-      {
-        SliderPanel.setPIDSliderSource(ap, av.getResidueShading(),
-                ap.getViewName());
-      }
-      // TODO: stretchGroup will always be not null. Is this a merge error ?
-      if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
-      {
-        // Edit end res position of selected group
-        changeEndRes = true;
-      }
-      else if ((stretchGroup != null)
-              && (stretchGroup.getStartRes() == res))
-      {
-        // Edit end res position of selected group
-        changeStartRes = true;
-      }
-      stretchGroup.getWidth();
+  private void createStretchGroup(int res, SequenceI sequence)
+  {
+    // Only if left mouse button do we want to change group sizes
+    // define a new group here
+    SequenceGroup sg = new SequenceGroup();
+    sg.setStartRes(res);
+    sg.setEndRes(res);
+    sg.addSequence(sequence, false);
+    av.setSelectionGroup(sg);
+    stretchGroup = sg;
+
+    if (av.getConservationSelected())
+    {
+      SliderPanel.setConservationSlider(ap, av.getResidueShading(),
+              ap.getViewName());
     }
 
-    seqCanvas.repaint();
+    if (av.getAbovePIDThreshold())
+    {
+      SliderPanel.setPIDSliderSource(ap, av.getResidueShading(),
+              ap.getViewName());
+    }
+    // TODO: stretchGroup will always be not null. Is this a merge error ?
+    // or is there a threading issue here?
+    if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
+    {
+      // Edit end res position of selected group
+      changeEndRes = true;
+    }
+    else if ((stretchGroup != null) && (stretchGroup.getStartRes() == res))
+    {
+      // Edit end res position of selected group
+      changeStartRes = true;
+    }
+    stretchGroup.getWidth();
+
   }
 
   /**
@@ -1758,12 +1779,11 @@ public class SeqPanel extends JPanel
    */
   void showPopupMenu(MouseEvent evt)
   {
-    final int res = findColumn(evt);
+    final int column = findColumn(evt);
     final int seq = findSeq(evt);
     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
     List<SequenceFeature> allFeatures = ap.getFeatureRenderer()
-            .findFeaturesAtRes(sequence.getDatasetSequence(),
-                    sequence.findPosition(res));
+            .findFeaturesAtColumn(sequence, column + 1);
     List<String> links = new ArrayList<>();
     for (SequenceFeature sf : allFeatures)
     {
@@ -1795,6 +1815,9 @@ public class SeqPanel extends JPanel
     {
       return;
     }
+
+    stretchGroup.removePropertyChangeListener(seqCanvas);
+
     // always do this - annotation has own state
     // but defer colourscheme update until hidden sequences are passed in
     boolean vischange = stretchGroup.recalcConservation(true);
@@ -1936,21 +1959,19 @@ public class SeqPanel extends JPanel
 
     mouseDragging = true;
 
-    if (scrollThread != null)
+    if ((scrollThread != null) && (scrollThread.isRunning()))
     {
       scrollThread.setEvent(evt);
     }
-
-    seqCanvas.repaint();
   }
 
   void scrollCanvas(MouseEvent evt)
   {
     if (evt == null)
     {
-      if (scrollThread != null)
+      if ((scrollThread != null) && (scrollThread.isRunning()))
       {
-        scrollThread.running = false;
+        scrollThread.stopScrolling();
         scrollThread = null;
       }
       mouseDragging = false;
@@ -1973,7 +1994,7 @@ public class SeqPanel extends JPanel
   {
     MouseEvent evt;
 
-    boolean running = false;
+    private volatile boolean threadRunning = true;
 
     public ScrollThread()
     {
@@ -1987,37 +2008,40 @@ public class SeqPanel extends JPanel
 
     public void stopScrolling()
     {
-      running = false;
+      threadRunning = false;
+    }
+
+    public boolean isRunning()
+    {
+      return threadRunning;
     }
 
     @Override
     public void run()
     {
-      running = true;
-
-      while (running)
+      while (threadRunning)
       {
         if (evt != null)
         {
           if (mouseDragging && (evt.getY() < 0)
                   && (av.getRanges().getStartSeq() > 0))
           {
-            running = av.getRanges().scrollUp(true);
+            av.getRanges().scrollUp(true);
           }
 
           if (mouseDragging && (evt.getY() >= getHeight()) && (av
                   .getAlignment().getHeight() > av.getRanges().getEndSeq()))
           {
-            running = av.getRanges().scrollUp(false);
+            av.getRanges().scrollUp(false);
           }
 
           if (mouseDragging && (evt.getX() < 0))
           {
-            running = av.getRanges().scrollRight(false);
+            av.getRanges().scrollRight(false);
           }
           else if (mouseDragging && (evt.getX() >= getWidth()))
           {
-            running = av.getRanges().scrollRight(true);
+            av.getRanges().scrollRight(true);
           }
         }