JAL-2778 try another way to draw sequences with reduced synchronisation
[jalview.git] / src / jalview / gui / SeqCanvas.java
index 58af47b..1be4728 100755 (executable)
@@ -43,6 +43,7 @@ import java.awt.Shape;
 import java.awt.image.BufferedImage;
 import java.beans.PropertyChangeEvent;
 import java.util.List;
+import java.util.function.Consumer;
 
 import javax.swing.JComponent;
 
@@ -295,48 +296,52 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       int endSeq = ranges.getEndSeq();
       int transX = 0;
       int transY = 0;
+      
+      gg.copyArea(horizontal * charWidth, vertical * charHeight,
+              img.getWidth(), img.getHeight(), -horizontal * charWidth,
+              -vertical * charHeight);
 
-    gg.copyArea(horizontal * charWidth, vertical * charHeight,
-            img.getWidth(), img.getHeight(), -horizontal * charWidth,
-            -vertical * charHeight);
-
-    if (horizontal > 0) // scrollbar pulled right, image to the left
-    {
-      transX = (endRes - startRes - horizontal) * charWidth;
-      startRes = endRes - horizontal;
-    }
-    else if (horizontal < 0)
-    {
-      endRes = startRes - horizontal;
-    }
-    else if (vertical > 0) // scroll down
-    {
-      startSeq = endSeq - vertical;
-
-      if (startSeq < ranges.getStartSeq())
-      { // ie scrolling too fast, more than a page at a time
-        startSeq = ranges.getStartSeq();
+      if (horizontal > 0) // scrollbar pulled right, image to the left
+      {
+        transX = (endRes - startRes - horizontal) * charWidth;
+        startRes = endRes - horizontal;
       }
-      else
+      else if (horizontal < 0)
       {
-        transY = img.getHeight() - ((vertical + 1) * charHeight);
+        endRes = startRes - horizontal;
       }
-    }
-    else if (vertical < 0)
-    {
-      endSeq = startSeq - vertical;
 
-      if (endSeq > ranges.getEndSeq())
+      if (vertical > 0) // scroll down
       {
-        endSeq = ranges.getEndSeq();
+        startSeq = endSeq - vertical;
+
+        if (startSeq < ranges.getStartSeq())
+        { // ie scrolling too fast, more than a page at a time
+          startSeq = ranges.getStartSeq();
+        }
+        else
+        {
+          transY = img.getHeight() - ((vertical + 1) * charHeight);
+        }
       }
-    }
+      else if (vertical < 0)
+      {
+        endSeq = startSeq - vertical;
 
-    gg.translate(transX, transY);
-    drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
-    gg.translate(-transX, -transY);
+        if (endSeq > ranges.getEndSeq())
+        {
+          endSeq = ranges.getEndSeq();
+        }
+      }
+
+      gg.translate(transX, transY);
+      drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
+      gg.translate(-transX, -transY);
 
-    repaint();
+      // Call repaint on alignment panel so that repaints from other alignment
+      // panel components can be aggregated. Otherwise performance of the
+      // overview window and others may be adversely affected.
+      av.getAlignPanel().repaint();
     } finally
     {
       fastpainting = false;
@@ -350,20 +355,20 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     
     int charHeight = av.getCharHeight();
     int charWidth = av.getCharWidth();
-
+    
     ViewportRanges ranges = av.getRanges();
-
+    
     int width = getWidth();
     int height = getHeight();
-
+    
     width -= (width % charWidth);
     height -= (height % charHeight);
-
+    
     // selectImage is the selection group outline image
     BufferedImage selectImage = drawSelectionGroup(
             ranges.getStartRes(), ranges.getEndRes(),
             ranges.getStartSeq(), ranges.getEndSeq());
-
+    
     if ((img != null) && (fastPaint
             || (getVisibleRect().width != g.getClipBounds().width)
             || (getVisibleRect().height != g.getClipBounds().height)))
@@ -387,16 +392,16 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
         gg = (Graphics2D) img.getGraphics();
         gg.setFont(av.getFont());
       }
-
+    
       if (av.antiAlias)
       {
         gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                 RenderingHints.VALUE_ANTIALIAS_ON);
       }
-
+    
       gg.setColor(Color.white);
       gg.fillRect(0, 0, img.getWidth(), img.getHeight());
-
+    
       if (av.getWrapAlignment())
       {
         drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
@@ -406,7 +411,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
         drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
                 ranges.getStartSeq(), ranges.getEndSeq(), 0);
       }
-
+    
       // lcimg is a local *copy* of img which we'll draw selectImage on top of
       BufferedImage lcimg = buildLocalImage(selectImage);
       g.drawImage(lcimg, 0, 0, this);
@@ -496,8 +501,11 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   private BufferedImage buildLocalImage(BufferedImage selectImage)
   {
     // clone the cached image
-    BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
-            img.getType());
+         BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
+                   img.getType());
+
+    // BufferedImage lcimg = new BufferedImage(img.getWidth(), img.getHeight(),
+    // img.getType());
     Graphics2D g2d = lcimg.createGraphics();
     g2d.drawImage(img, 0, 0, null);
 
@@ -536,8 +544,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
     try
     {
-      lcimg = new BufferedImage(width, height,
-              BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
+       lcimg = new BufferedImage(width, height,
+                BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
     } catch (OutOfMemoryError er)
     {
       System.gc();
@@ -1091,71 +1099,97 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
    * @param yOffset
    *          vertical offset at which to draw (for wrapped alignments)
    */
-  private void draw(Graphics g, int startRes, int endRes, int startSeq,
-          int endSeq, int offset)
+  private void draw(final Graphics g, final int startRes, final int endRes,
+          final int startSeq, final int endSeq, final int offset)
   {
-    int charHeight = av.getCharHeight();
-    int charWidth = av.getCharWidth();
-
     g.setFont(av.getFont());
     seqRdr.prepare(g, av.isRenderGaps());
 
-    SequenceI nextSeq;
-
-    // / First draw the sequences
-    // ///////////////////////////
-    for (int i = startSeq; i <= endSeq; i++)
+    // First draw the sequences
+    av.getAlignment().forEachSequence(new Consumer<SequenceI>()
     {
-      nextSeq = av.getAlignment().getSequenceAt(i);
-      if (nextSeq == null)
-      {
-        // occasionally, a race condition occurs such that the alignment row is
-        // empty
-        continue;
-      }
-      seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
-              startRes, endRes, offset + ((i - startSeq) * charHeight));
+      int i = startSeq;
 
-      if (av.isShowSequenceFeatures())
+      @Override
+      public void accept(SequenceI s)
       {
-        fr.drawSequence(g, nextSeq, startRes, endRes,
-                offset + ((i - startSeq) * charHeight), false);
+        int heightPosition = offset + ((i - startSeq) * av.getCharHeight());
+        drawSequence(s, g, startRes, endRes, heightPosition, i);
+        i++;
       }
+    }, startSeq, endSeq + 1);
 
-      /*
-       * highlight search Results once sequence has been drawn
-       */
-      if (av.hasSearchResults())
+    // now selection groups
+    if (av.getSelectionGroup() != null
+            || av.getAlignment().getGroups().size() > 0)
+    {
+      drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
+    }
+
+  }
+
+  /**
+   * Draw a single sequence
+   * 
+   * @param nextSeq
+   *          the next sequence to draw
+   * @param g
+   *          graphics context
+   * @param startRes
+   *          offset of the first column in the visible region (0..)
+   * @param endRes
+   *          offset of the last column in the visible region (0..)
+   * @param heightPosition
+   *          vertical location of the sequence in the alignment
+   * @param i
+   *          index of sequence
+   */
+  private void drawSequence(SequenceI nextSeq, Graphics g, int startRes,
+          int endRes, int heightPosition, int i)
+  {
+    int charWidth = av.getCharWidth();
+
+    if (nextSeq == null)
+    {
+      // occasionally, a race condition occurs such that the alignment row is
+      // empty
+      // TODO Don't think this will happen any more?
+      return;
+    }
+    seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
+            startRes, endRes, heightPosition);
+
+    if (av.isShowSequenceFeatures())
+    {
+      fr.drawSequence(g, nextSeq, startRes, endRes, heightPosition, false);
+    }
+
+    /*
+     * highlight search Results once sequence has been drawn
+     */
+    if (av.hasSearchResults())
+    {
+      SearchResultsI searchResults = av.getSearchResults();
+      int[] visibleResults = searchResults.getResults(nextSeq, startRes,
+              endRes);
+      if (visibleResults != null)
       {
-        SearchResultsI searchResults = av.getSearchResults();
-        int[] visibleResults = searchResults.getResults(nextSeq,
-                startRes, endRes);
-        if (visibleResults != null)
+        for (int r = 0; r < visibleResults.length; r += 2)
         {
-          for (int r = 0; r < visibleResults.length; r += 2)
-          {
-            seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
-                    visibleResults[r + 1], (visibleResults[r] - startRes)
-                            * charWidth, offset
-                            + ((i - startSeq) * charHeight));
-          }
+          seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
+                  visibleResults[r + 1],
+                  (visibleResults[r] - startRes) * charWidth,
+                  heightPosition);
         }
       }
-
-      if (av.cursorMode && cursorY == i && cursorX >= startRes
-              && cursorX <= endRes)
-      {
-        seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
-                offset + ((i - startSeq) * charHeight));
-      }
     }
 
-    if (av.getSelectionGroup() != null
-            || av.getAlignment().getGroups().size() > 0)
+    if (av.cursorMode && cursorY == i && cursorX >= startRes
+            && cursorX <= endRes)
     {
-      drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
+      seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
+              heightPosition);
     }
-
   }
 
   void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
@@ -1687,34 +1721,37 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       {
         scrollX = -range;
       }
+    }
 
-      // Both scrolling and resizing change viewport ranges: scrolling changes
-      // both start and end points, but resize only changes end values.
-      // Here we only want to fastpaint on a scroll, with resize using a normal
-      // paint, so scroll events are identified as changes to the horizontal or
-      // vertical start value.
-      if (eventName.equals(ViewportRanges.STARTRES))
-      {
-         if (av.getWrapAlignment())
-          {
-            fastPaintWrapped(scrollX);
-          }
-          else
-          {
-            fastPaint(scrollX, 0);
-          }
-      }
-      else if (eventName.equals(ViewportRanges.STARTSEQ))
+    // Both scrolling and resizing change viewport ranges: scrolling changes
+    // both start and end points, but resize only changes end values.
+    // Here we only want to fastpaint on a scroll, with resize using a normal
+    // paint, so scroll events are identified as changes to the horizontal or
+    // vertical start value.
+    if (eventName.equals(ViewportRanges.STARTRES))
+    {
+      if (av.getWrapAlignment())
       {
-        // scroll
-        fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
+        fastPaintWrapped(scrollX);
       }
-      else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
+      else
       {
-        fastPaint(scrollX, ((int[]) evt.getNewValue())[1]
-                - ((int[]) evt.getOldValue())[1]);
+        fastPaint(scrollX, 0);
       }
     }
+    else if (eventName.equals(ViewportRanges.STARTSEQ))
+    {
+      // scroll
+      fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
+    }
+    else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
+    {
+      fastPaint(scrollX, 0);
+      // bizarrely, we only need to scroll on the x value here as fastpaint
+      // copies the full height of the image anyway. Passing in the y value
+      // causes nasty repaint artefacts, which only disappear on a full
+      // repaint.
+    }
   }
 
   /**