JAL-2778 try another way to draw sequences with reduced synchronisation
[jalview.git] / src / jalview / gui / SeqCanvas.java
index 6c6e87a..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;
+
+        if (endSeq > ranges.getEndSeq())
+        {
+          endSeq = ranges.getEndSeq();
+        }
       }
-    }
 
-    gg.translate(transX, transY);
-    drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
-    gg.translate(-transX, -transY);
+      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();
@@ -838,10 +846,12 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
        * white fill region of scale above and hidden column markers
        * (to support incremental fast paint of image)
        */
+      g.translate(labelWidthWest, 0);
       g.setColor(Color.white);
       g.fillRect(0, ypos - wrappedSpaceAboveAlignment, viewportWidth
               * charWidth + labelWidthWest, wrappedSpaceAboveAlignment);
       g.setColor(Color.black);
+      g.translate(-labelWidthWest, 0);
 
       g.translate(labelWidthWest, 0);
 
@@ -910,9 +920,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
           int canvasWidth,
           int canvasHeight, int startRes)
   {
-       int charHeight = av.getCharHeight();
-       int charWidth = av.getCharWidth();
-         
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
+      
     // height gap above each panel
     int hgap = charHeight;
     if (av.getScaleAboveWrapped())
@@ -1089,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,
@@ -1274,8 +1310,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
           int startRes, int endRes, int startSeq, int endSeq, int offset)
   {
-       int charWidth = av.getCharWidth();
-         
+    int charWidth = av.getCharWidth();
+          
     if (!av.hasHiddenColumns())
     {
       drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
@@ -1337,9 +1373,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
           int startRes, int endRes, int startSeq, int endSeq,
           int verticalOffset)
   {
-       int charHeight = av.getCharHeight();
-       int charWidth = av.getCharWidth();
-         
+        int charHeight = av.getCharHeight();
+        int charWidth = av.getCharWidth();
+          
     int visWidth = (endRes - startRes + 1) * charWidth;
 
     int oldY = -1;
@@ -1656,48 +1692,66 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     {
       fastPaint = true;
       repaint();
+      return;
     }
-    else if (eventName.equals(ViewportRanges.STARTRES))
+
+    int scrollX = 0;
+    if (eventName.equals(ViewportRanges.STARTRES)
+            || eventName.equals(ViewportRanges.STARTRESANDSEQ))
     {
-      int scrollX = 0;
+      // Make sure we're not trying to draw a panel
+      // larger than the visible window
       if (eventName.equals(ViewportRanges.STARTRES))
       {
-        // Make sure we're not trying to draw a panel
-        // larger than the visible window
-        ViewportRanges vpRanges = av.getRanges();
         scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
-        int range = vpRanges.getEndRes() - vpRanges.getStartRes() + 1;
-        if (scrollX > range)
-        {
-          scrollX = range;
-        }
-        else if (scrollX < -range)
-        {
-          scrollX = -range;
-        }
+      }
+      else
+      {
+        scrollX = ((int[]) evt.getNewValue())[0]
+                - ((int[]) evt.getOldValue())[0];
+      }
+      ViewportRanges vpRanges = av.getRanges();
 
-        // 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.
-        
-        // scroll - startres and endres both change
-         if (av.getWrapAlignment())
-        {
-          fastPaintWrapped(scrollX);
-        }
-        else
-        {
-          fastPaint(scrollX, 0);
-        }
+      int range = vpRanges.getEndRes() - vpRanges.getStartRes();
+      if (scrollX > range)
+      {
+        scrollX = range;
+      }
+      else if (scrollX < -range)
+      {
+        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 if (eventName.equals(ViewportRanges.STARTSEQ))
+      else
       {
-        // scroll
-        fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
+        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.
+    }
   }
 
   /**