JAL-2665 Changes for review comments; printing with selgroup working
[jalview.git] / src / jalview / gui / SeqCanvas.java
index 646ecf8..955a9e6 100755 (executable)
@@ -55,7 +55,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 {
   final FeatureRenderer fr;
 
-  final SequenceRenderer sr;
+  final SequenceRenderer seqRdr;
 
   BufferedImage img;
 
@@ -73,6 +73,14 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
   int cursorY = 0;
 
+  int charHeight = 0;
+
+  int charWidth = 0;
+
+  boolean fastpainting = false;
+
+  AnnotationPanel annotations;
+
   /**
    * Creates a new SeqCanvas object.
    * 
@@ -84,7 +92,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     this.av = ap.av;
     updateViewport();
     fr = new FeatureRenderer(ap);
-    sr = new SequenceRenderer(av);
+    seqRdr = new SequenceRenderer(av);
     setLayout(new BorderLayout());
     PaintRefresher.Register(this, av.getSequenceSetId());
     setBackground(Color.white);
@@ -94,7 +102,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
   public SequenceRenderer getSequenceRenderer()
   {
-    return sr;
+    return seqRdr;
   }
 
   public FeatureRenderer getFeatureRenderer()
@@ -102,8 +110,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     return fr;
   }
 
-  int charHeight = 0, charWidth = 0;
-
   private void updateViewport()
   {
     charHeight = av.getCharHeight();
@@ -263,7 +269,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     }
   }
 
-  boolean fastpainting = false;
 
   /**
    * need to make this thread safe move alignment rendering in response to
@@ -353,24 +358,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
     updateViewport();
 
-    // img is a cached version of the last view we drew
-    // selectImage will hold any selection we have
-    // lcimg is a local *copy* of img which we'll draw selectImage on top of
-
     ViewportRanges ranges = av.getRanges();
-    BufferedImage selectImage = drawSelectionGroup(
-            av.getRanges().getStartRes(), av.getRanges().getEndRes(),
-            ranges.getStartSeq(), ranges.getEndSeq());
-
-    if (fastPaint || (getVisibleRect().width != g.getClipBounds().width)
-            || (getVisibleRect().height != g.getClipBounds().height))
-    {
-      BufferedImage lcimg = buildLocalImage(selectImage);
-      g.drawImage(lcimg, 0, 0, this);
-
-      fastPaint = false;
-      return;
-    }
 
     int width = getWidth();
     int height = getHeight();
@@ -378,44 +366,83 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     width -= (width % charWidth);
     height -= (height % charHeight);
 
-    if ((width < 1) || (height < 1))
-    {
-      return;
-    }
+    // selectImage is the selection group outline image
+    BufferedImage selectImage = drawSelectionGroup(
+            ranges.getStartRes(), ranges.getEndRes(),
+            ranges.getStartSeq(), ranges.getEndSeq());
 
-    if (img == null || width != img.getWidth() || height != img.getHeight())
+    if ((img != null) && (fastPaint
+            || (getVisibleRect().width != g.getClipBounds().width)
+            || (getVisibleRect().height != g.getClipBounds().height)))
     {
-      img = setupImage();
-      gg = (Graphics2D) img.getGraphics();
-      gg.setFont(av.getFont());
+      BufferedImage lcimg = buildLocalImage(selectImage);
+      g.drawImage(lcimg, 0, 0, this);
+      fastPaint = false;
     }
-    if (img == null)
+    else if ((width > 0) && (height > 0))
     {
-      return;
-    }
+      // img is a cached version of the last view we drew, if any
+      // if we have no img or the size has changed, make a new one
+      if (img == null || width != img.getWidth()
+              || height != img.getHeight())
+      {
+        img = setupImage();
+        if (img == null)
+        {
+          return;
+        }
+        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());
-    }
-    else
-    {
-      drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
-              ranges.getStartSeq(), ranges.getEndSeq(), 0);
+      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());
+      }
+      else
+      {
+        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);
     }
+  }
 
-    BufferedImage lcimg = buildLocalImage(selectImage);
-    g.drawImage(lcimg, 0, 0, this);
+  /**
+   * Draw an alignment panel for printing
+   * 
+   * @param g1
+   *          Graphics object to draw with
+   * @param startRes
+   *          start residue of print area
+   * @param endRes
+   *          end residue of print area
+   * @param startSeq
+   *          start sequence of print area
+   * @param endSeq
+   *          end sequence of print area
+   */
+  public void drawPanelForPrint(Graphics g1, int startRes, int endRes,
+          int startSeq, int endSeq)
+  {
+    BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
+            startSeq, endSeq);
+    drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
+    ((Graphics2D) g1).setComposite(
+            AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
+    g1.drawImage(selectImage, 0, 0, this);
   }
 
   /*
@@ -442,12 +469,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     return lcimg;
   }
 
-  private void paintSeqGroup()
-  {
-    fastPaint = true;
-    repaint();
-  }
-
+  /*
+   * Set up a buffered image of the correct height and size for the sequence canvas
+   */
   private BufferedImage setupImage()
   {
     BufferedImage lcimg = null;
@@ -471,7 +495,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     {
       System.gc();
       System.err.println(
-              "Selection Group image OutOfMemory Redraw Error.\n" + er);
+              "Group image OutOfMemory Redraw Error.\n" + er);
       new OOMWarning("Creating alignment image for display", er);
 
       return null;
@@ -683,10 +707,14 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     }
   }
 
-  public void drawWrappedSelection(Graphics2D g, SequenceGroup group,
+  /*
+   * Draw a selection group over a wrapped alignment
+   */
+  private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
           int canvasWidth,
           int canvasHeight, int startRes)
   {
+    // height gap above each panel
     int hgap = charHeight;
     if (av.getScaleAboveWrapped())
     {
@@ -696,8 +724,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / charWidth;
     int cHeight = av.getAlignment().getHeight() * charHeight;
 
+    int startx = startRes;
     int endx;
-    int ypos = hgap;
+    int ypos = hgap; // vertical offset
     int maxwidth = av.getAlignment().getWidth();
 
     if (av.hasHiddenColumns())
@@ -706,9 +735,12 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
               .findColumnPosition(maxwidth);
     }
 
-    while ((ypos <= canvasHeight) && (startRes < maxwidth))
+    // chop the wrapped alignment extent up into panel-sized blocks and treat
+    // each block as if it were a block from an unwrapped alignment
+    while ((ypos <= canvasHeight) && (startx < maxwidth))
     {
-      endx = startRes + cWidth - 1;
+      // set end value to be start + width, or maxwidth, whichever is smaller
+      endx = startx + cWidth - 1;
 
       if (endx > maxwidth)
       {
@@ -717,20 +749,20 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
       g.translate(LABEL_WEST, 0);
 
-      drawUnwrappedSelection(g, group, startRes, endx, 0,
+      drawUnwrappedSelection(g, group, startx, endx, 0,
               av.getAlignment().getHeight() - 1,
               ypos);
 
       g.translate(-LABEL_WEST, 0);
 
+      // update vertical offset
       ypos += cHeight + getAnnotationHeight() + hgap;
 
-      startRes += cWidth;
+      // update horizontal offset
+      startx += cWidth;
     }
   }
 
-  AnnotationPanel annotations;
-
   int getAnnotationHeight()
   {
     if (!av.isShowAnnotation())
@@ -746,23 +778,23 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     return annotations.adjustPanelHeight();
   }
 
-  /**
-   * DOCUMENT ME!
+  /*
+   * Draw an alignment panel for printing
    * 
    * @param g1
-   *          DOCUMENT ME!
+   *          Graphics object to draw with
    * @param startRes
-   *          DOCUMENT ME!
+   *          start residue of print area
    * @param endRes
-   *          DOCUMENT ME!
+   *          end residue of print area
    * @param startSeq
-   *          DOCUMENT ME!
+   *          start sequence of print area
    * @param endSeq
-   *          DOCUMENT ME!
+   *          end sequence of print area
    * @param offset
-   *          DOCUMENT ME!
+   *          vertical offset
    */
-  public void drawPanel(Graphics g1, int startRes, int endRes,
+  private void drawPanel(Graphics g1, int startRes, int endRes,
           int startSeq, int endSeq, int offset)
   {
     updateViewport();
@@ -831,7 +863,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
           int endSeq, int offset)
   {
     g.setFont(av.getFont());
-    sr.prepare(g, av.isRenderGaps());
+    seqRdr.prepare(g, av.isRenderGaps());
 
     SequenceI nextSeq;
 
@@ -846,7 +878,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
         // empty
         continue;
       }
-      sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
+      seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
               startRes, endRes, offset + ((i - startSeq) * charHeight));
 
       if (av.isShowSequenceFeatures())
@@ -865,7 +897,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
         {
           for (int r = 0; r < visibleResults.length; r += 2)
           {
-            sr.drawHighlightedText(nextSeq, visibleResults[r],
+            seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
                     visibleResults[r + 1], (visibleResults[r] - startRes)
                             * charWidth, offset
                             + ((i - startSeq) * charHeight));
@@ -876,7 +908,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       if (av.cursorMode && cursorY == i && cursorX >= startRes
               && cursorX <= endRes)
       {
-        sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
+        seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
                 offset + ((i - startSeq) * charHeight));
       }
     }
@@ -899,12 +931,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     // ///////////////////////////////////
 
     SequenceGroup group = null;
-
-    int sx = -1;
-    int sy = -1;
-    int ex = -1;
     int groupIndex = -1;
-    int visWidth = (endRes - startRes + 1) * charWidth;
 
     if (av.getAlignment().getGroups().size() > 0)
     {
@@ -914,150 +941,14 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
     if (group != null)
     {
+      g.setStroke(new BasicStroke());
+      g.setColor(group.getOutlineColour());
+      
       do
       {
-        int oldY = -1;
-        int i = 0;
-        boolean inGroup = false;
-        int top = -1;
-        int bottom = -1;
-
-        for (i = startSeq; i <= endSeq; i++)
-        {
-          // position of start residue of group relative to startRes, in pixels
-          sx = (group.getStartRes() - startRes) * charWidth;
-          sy = offset + ((i - startSeq) * charHeight);
-          // width of group in pixels
-          ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
-                  - 1;
-
-          if (sx + ex < 0 || sx > visWidth)
-          {
-            continue;
-          }
-
-          if ((sx <= (endRes - startRes) * charWidth)
-                  && group.getSequences(null).contains(
-                          av.getAlignment().getSequenceAt(i)))
-          {
-            if ((bottom == -1)
-                    && !group.getSequences(null).contains(
-                            av.getAlignment().getSequenceAt(i + 1)))
-            {
-              bottom = sy + charHeight;
-            }
-
-            if (!inGroup)
-            {
-              if (((top == -1) && (i == 0))
-                      || !group.getSequences(null).contains(
-                              av.getAlignment().getSequenceAt(i - 1)))
-              {
-                top = sy;
-              }
-
-              oldY = sy;
-              inGroup = true;
-
-              g.setStroke(new BasicStroke());
-              g.setColor(group.getOutlineColour());
-            }
-          }
-          else
-          {
-            if (inGroup)
-            {
-              // if start position is visible, draw vertical line to left of
-              // group
-              if (sx >= 0 && sx < visWidth)
-              {
-                g.drawLine(sx, oldY, sx, sy);
-              }
-
-              // if end position is visible, draw vertical line to right of
-              // group
-              if (sx + ex < visWidth)
-              {
-                g.drawLine(sx + ex, oldY, sx + ex, sy);
-              }
-
-              if (sx < 0)
-              {
-                ex += sx;
-                sx = 0;
-              }
-
-              if (sx + ex > visWidth)
-              {
-                ex = visWidth;
-              }
-              else if (sx + ex >= (endRes - startRes + 1) * charWidth)
-              {
-                ex = (endRes - startRes + 1) * charWidth;
-              }
-
-              // draw horizontal line at top of group
-              if (top != -1)
-              {
-                g.drawLine(sx, top, sx + ex, top);
-                top = -1;
-              }
-
-              // draw horizontal line at bottom of group
-              if (bottom != -1)
-              {
-                g.drawLine(sx, bottom, sx + ex, bottom);
-                bottom = -1;
-              }
-
-              inGroup = false;
-            }
-          }
-        }
-
-        if (inGroup)
-        {
-          sy = offset + ((i - startSeq) * charHeight);
-          if (sx >= 0 && sx < visWidth)
-          {
-            g.drawLine(sx, oldY, sx, sy);
-          }
-
-          if (sx + ex < visWidth)
-          {
-            g.drawLine(sx + ex, oldY, sx + ex, sy);
-          }
-
-          if (sx < 0)
-          {
-            ex += sx;
-            sx = 0;
-          }
-
-          if (sx + ex > visWidth)
-          {
-            ex = visWidth;
-          }
-          else if (sx + ex >= (endRes - startRes + 1) * charWidth)
-          {
-            ex = (endRes - startRes + 1) * charWidth;
-          }
-
-          if (top != -1)
-          {
-            g.drawLine(sx, top, sx + ex, top);
-            top = -1;
-          }
-
-          if (bottom != -1)
-          {
-            g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
-            bottom = -1;
-          }
-
-          inGroup = false;
-        }
-
+        drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
+                endSeq, offset);
+        
         groupIndex++;
 
         g.setStroke(new BasicStroke());
@@ -1099,11 +990,12 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
     // set up drawing colour
     Graphics2D g = (Graphics2D) selectionImage.getGraphics();
-    // g.translate(LABEL_WEST, 0);
+
     // set background to transparent
     g.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f));
     g.fillRect(0, 0, selectionImage.getWidth(), selectionImage.getHeight());
 
+    // set up foreground to draw red dashed line
     g.setComposite(AlphaComposite.Src);
     g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
             BasicStroke.JOIN_ROUND, 3f, new float[]
@@ -1125,12 +1017,22 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     return selectionImage;
   }
 
+  /*
+   * Draw a selection group over an unwrapped alignment
+   * @param g graphics object to draw with
+   * @param group selection group
+   * @param startRes start residue of area to draw
+   * @param endRes end residue of area to draw
+   * @param startSeq start sequence of area to draw
+   * @param endSeq end sequence of area to draw
+   * @param offset vertical offset (used when called from wrapped alignment code)
+   */
   private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
           int startRes, int endRes, int startSeq, int endSeq, int offset)
   {
     if (!av.hasHiddenColumns())
     {
-      drawSelectionGroupPart(g, group, startRes, endRes, startSeq, endSeq,
+      drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
               offset);
     }
     else
@@ -1155,7 +1057,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
         blockEnd = hideStart - 1;
 
         g.translate(screenY * charWidth, 0);
-        drawSelectionGroupPart(g, group,
+        drawPartialGroupOutline(g, group,
                 blockStart, blockEnd, startSeq, endSeq, offset);
 
         g.translate(-screenY * charWidth, 0);
@@ -1174,7 +1076,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
         // remaining visible region to render
         blockEnd = blockStart + (endRes - startRes) - screenY;
         g.translate(screenY * charWidth, 0);
-        drawSelectionGroupPart(g, group,
+        drawPartialGroupOutline(g, group,
                 blockStart, blockEnd, startSeq, endSeq, offset);
         
         g.translate(-screenY * charWidth, 0);
@@ -1185,7 +1087,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   /*
    * Draw the selection group as a separate image and overlay
    */
-  private void drawSelectionGroupPart(Graphics2D g, SequenceGroup group,
+  private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
           int startRes, int endRes, int startSeq, int endSeq,
           int verticalOffset)
   {
@@ -1263,6 +1165,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
             sx = 0;
           }
 
+          // don't let width extend beyond current block, or group extent
+          // fixes JAL-2672
           if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
           {
             xwidth = (endRes - startRes + 1) * charWidth - sx;
@@ -1330,7 +1234,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       inGroup = false;
     }
   }
-
+  
   /**
    * DOCUMENT ME!
    * 
@@ -1353,7 +1257,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
     if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
     {
-      paintSeqGroup();
+      fastPaint = true;
+      repaint();
     }
     else if (av.getWrapAlignment())
     {