JAL-2609 tweaks to, and tests for, calculateWrappedGeometry
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 22 Aug 2017 10:19:21 +0000 (11:19 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 22 Aug 2017 10:19:21 +0000 (11:19 +0100)
src/jalview/gui/SeqCanvas.java
test/jalview/gui/SeqCanvasTest.java [new file with mode: 0644]

index 46f2d84..a4c7da5 100755 (executable)
@@ -77,10 +77,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
   int cursorY = 0;
 
-  int charHeight = 0;
-
-  int charWidth = 0;
-
   private AnnotationPanel annotations;
 
   /*
@@ -105,7 +101,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   public SeqCanvas(AlignmentPanel ap)
   {
     this.av = ap.av;
-    updateViewport();
     fr = new FeatureRenderer(ap);
     sr = new SequenceRenderer(av);
     setLayout(new BorderLayout());
@@ -125,12 +120,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     return fr;
   }
 
-  private void updateViewport()
-  {
-    charHeight = av.getCharHeight();
-    charWidth = av.getCharWidth();
-  }
-
   /**
    * Draws the scale above a region of a wrapped alignment, consisting of a
    * column number every major interval (10 columns).
@@ -147,7 +136,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
    */
   private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
   {
-    updateViewport();
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
 
     /*
      * white fill the scale space (for the fastPaint case)
@@ -201,6 +191,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   void drawVerticalScale(Graphics g, int startx, int endx, int ypos,
           boolean left)
   {
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
+
     ypos += charHeight;
 
     if (av.hasHiddenColumns())
@@ -297,7 +290,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
     try
     {
-      updateViewport();
+      int charHeight = av.getCharHeight();
+      int charWidth = av.getCharWidth();
 
       ViewportRanges ranges = av.getRanges();
       int startRes = ranges.getStartRes();
@@ -356,7 +350,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   @Override
   public void paintComponent(Graphics g)
   {
-    updateViewport();
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
     BufferedImage lcimg = img; // take reference since other threads may null
     // img and call later.
     super.paintComponent(g);
@@ -436,6 +431,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
    */
   public int getWrappedCanvasWidth(int canvasWidth)
   {
+    int charWidth = av.getCharWidth();
+
     FontMetrics fm = getFontMetrics(av.getFont());
 
     labelWidthEast = 0;
@@ -500,8 +497,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   public void drawWrappedPanel(Graphics g, int canvasWidth,
           int canvasHeight, final int startColumn)
   {
-    updateViewport();
-
     int wrappedWidthInResidues = calculateWrappedGeometry(canvasWidth,
             canvasHeight);
 
@@ -514,20 +509,19 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
      * draw one width at a time (including any scales or annotation shown),
      * until we have run out of either alignment or vertical space available
      */
-    int yposMax = canvasHeight;
-    // ensure room for at least one sequence
-    yposMax -= wrappedSpaceAboveAlignment - charHeight;
     int ypos = wrappedSpaceAboveAlignment;
     int maxWidth = ranges.getVisibleAlignmentWidth();
 
     int start = startColumn;
-    while ((ypos <= yposMax) && (start < maxWidth))
+    int currentWidth = 0;
+    while ((currentWidth < wrappedVisibleWidths) && (start < maxWidth))
     {
       int endColumn = Math
               .min(maxWidth, start + wrappedWidthInResidues - 1);
       drawWrappedWidth(g, ypos, start, endColumn, canvasHeight);
       ypos += wrappedRepeatHeightPx;
       start += wrappedWidthInResidues;
+      currentWidth++;
     }
 
     drawWrappedDecorators(g, startColumn);
@@ -549,6 +543,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
    */
   protected int calculateWrappedGeometry(int canvasWidth, int canvasHeight)
   {
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
+
     /*
      * width of labels in pixels left and right (if shown)
      */
@@ -563,6 +560,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
     /*
      * vertical space in pixels between wrapped widths of alignment
+     * - one character height, or two if scale above is drawn
      */
     wrappedSpaceAboveAlignment = charHeight
             * (av.getScaleAboveWrapped() ? 2 : 1);
@@ -577,7 +575,16 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     wrappedRepeatHeightPx += getAnnotationHeight();
 
     /*
-     * number of visible widths (the last one may be part height)
+     * number of residue columns we can show in each row;
+     * this is just canvas width less scale left and right (if shown), 
+     * as a whole multiple of character widths 
+     */
+    int wrappedWidthInResidues = (canvasWidth - labelWidthEast - labelWidthWest)
+            / charWidth;
+
+    /*
+     * number of visible widths (the last one may be part height),
+     * ensuring a part height includes at least one sequence
      */
     ViewportRanges ranges = av.getRanges();
     int xMax = ranges.getVisibleAlignmentWidth();
@@ -591,19 +598,13 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     /*
      *  limit visibleWidths to not exceed width of alignment
      */
-    int viewportWidth = ranges.getViewportWidth();
-    int maxWidths = (xMax - ranges.getStartRes()) / viewportWidth;
-    if (xMax % viewportWidth > 0)
+    int maxWidths = (xMax - ranges.getStartRes()) / wrappedWidthInResidues;
+    if (xMax % wrappedWidthInResidues > 0)
     {
       maxWidths++;
     }
     wrappedVisibleWidths = Math.min(wrappedVisibleWidths, maxWidths);
 
-    /*
-     * number of whole width residue columns we can show in each row
-     */
-    int wrappedWidthInResidues = (canvasWidth - labelWidthEast - labelWidthWest)
-            / charWidth;
     return wrappedWidthInResidues;
   }
 
@@ -620,6 +621,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   protected void drawWrappedWidth(Graphics g, int ypos,
           int startColumn, int endColumn, int canvasHeight)
   {
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
+
     ViewportRanges ranges = av.getRanges();
     int viewportWidth = ranges.getViewportWidth();
 
@@ -687,6 +691,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
    */
   protected void drawWrappedDecorators(Graphics g, int startColumn)
   {
+    int charWidth = av.getCharWidth();
+
     g.setFont(av.getFont());
     g.setColor(Color.black);
 
@@ -747,6 +753,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   protected void drawHiddenColumnMarkers(Graphics g, int ypos,
           int startColumn, int endColumn)
   {
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
+
     g.setColor(Color.blue);
     HiddenColumns hidden = av.getAlignment().getHiddenColumns();
     List<Integer> positions = hidden.findHiddenRegionPositions();
@@ -809,7 +818,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   public void drawPanel(Graphics g1, final int startRes, final int endRes,
           final int startSeq, final int endSeq, final int yOffset)
   {
-    updateViewport();
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
+
     if (!av.hasHiddenColumns())
     {
       draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
@@ -899,6 +910,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   private void draw(Graphics g, int startRes, int endRes, int startSeq,
           int endSeq, int offset)
   {
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
+
     g.setFont(av.getFont());
     sr.prepare(g, av.isRenderGaps());
 
@@ -963,6 +977,9 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
           int startSeq, int endSeq, int offset)
   {
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
+
     Graphics2D g = (Graphics2D) g1;
     //
     // ///////////////////////////////////
@@ -1170,14 +1187,11 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
       return false;
     }
     boolean wrapped = av.getWrapAlignment();
-
     try
     {
       fastPaint = !noFastPaint;
       fastpainting = fastPaint;
 
-      updateViewport();
-
       /*
        * to avoid redrawing the whole visible region, we instead
        * redraw just the minimal regions to remove previous highlights
@@ -1445,6 +1459,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
 
     ViewportRanges ranges = av.getRanges();
     int viewportWidth = ranges.getViewportWidth();
+    int charWidth = av.getCharWidth();
 
     /**
      * draw full height alignment in the second last row, last columns, if the
@@ -1478,6 +1493,8 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     }
 
     /*
+     * draw newly visible columns in last wrapped width (none if we
+     * have reached the end of the alignment)
      * y-offset for drawing last width is height of widths above,
      * plus one gap row
      */
@@ -1486,7 +1503,6 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
             + wrappedSpaceAboveAlignment;
     int endRes = ranges.getEndRes();
     endRes += widthsAbove * viewportWidth;
-    endRes = Math.min(endRes, ranges.getVisibleAlignmentWidth());
     int startRes = endRes - columns + 1;
 
     /*
@@ -1503,7 +1519,10 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     gg.setFont(av.getFont());
     gg.setColor(Color.black);
 
-    drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
+    if (startRes < ranges.getVisibleAlignmentWidth())
+    {
+      drawWrappedWidth(gg, ypos, startRes, endRes, canvasHeight);
+    }
 
     /*
      * and finally, white fill any space below the visible alignment
@@ -1530,6 +1549,7 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     {
       return;
     }
+    int charWidth = av.getCharWidth();
 
     int canvasHeight = getHeight();
     ViewportRanges ranges = av.getRanges();
@@ -1618,11 +1638,13 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
     {
       return false;
     }
-  
+    int charHeight = av.getCharHeight();
+
     boolean matchFound = false;
 
+    calculateWrappedGeometry(getWidth(), getHeight());
     int wrappedWidth = av.getWrappedWidth();
-    int wrappedHeight = getRepeatHeightWrapped();
+    int wrappedHeight = wrappedRepeatHeightPx;
 
     ViewportRanges ranges = av.getRanges();
     int canvasHeight = getHeight();
@@ -1719,25 +1741,4 @@ public class SeqCanvas extends JComponent implements ViewportListenerI
   
     return matchFound;
   }
-
-  /**
-   * Answers the height in pixels of a repeating section of the wrapped
-   * alignment, including space above, scale above if shown, sequences, and
-   * annotation panel if shown
-   * 
-   * @return
-   */
-  protected int getRepeatHeightWrapped()
-  {
-    // gap (and maybe scale) above
-    int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
-
-    // add sequences
-    repeatHeight += av.getRanges().getViewportHeight() * charHeight;
-
-    // add annotations panel height if shown
-    repeatHeight += getAnnotationHeight();
-
-    return repeatHeight;
-  }
 }
diff --git a/test/jalview/gui/SeqCanvasTest.java b/test/jalview/gui/SeqCanvasTest.java
new file mode 100644 (file)
index 0000000..e387322
--- /dev/null
@@ -0,0 +1,184 @@
+package jalview.gui;
+
+import static org.testng.Assert.assertEquals;
+
+import jalview.datamodel.AlignmentI;
+import jalview.io.DataSourceType;
+import jalview.io.FileLoader;
+
+import java.awt.Font;
+import java.awt.FontMetrics;
+
+import junit.extensions.PA;
+
+import org.testng.annotations.Test;
+
+import sun.swing.SwingUtilities2;
+
+public class SeqCanvasTest
+{
+  /**
+   * Test the method that computes wrapped width in residues, height of wrapped
+   * widths in pixels, and the number of widths visible
+   */
+  @Test(groups = "Functional")
+  public void testCalculateWrappedGeometry_noAnnotations()
+  {
+    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(
+            "examples/uniref50.fa", DataSourceType.FILE);
+    AlignViewport av = af.getViewport();
+    AlignmentI al = av.getAlignment();
+    assertEquals(al.getWidth(), 157);
+    assertEquals(al.getHeight(), 15);
+
+    av.setWrapAlignment(true);
+    av.setFont(new Font("SansSerif", Font.PLAIN, 14), true);
+    int charHeight = av.getCharHeight();
+    int charWidth = av.getCharWidth();
+    assertEquals(charHeight, 17);
+    assertEquals(charWidth, 12);
+
+    SeqCanvas testee = af.alignPanel.getSeqPanel().seqCanvas;
+
+    /*
+     * first with scales above, left, right
+     */
+    av.setShowAnnotation(false);
+    av.setScaleAboveWrapped(true);
+    av.setScaleLeftWrapped(true);
+    av.setScaleRightWrapped(true);
+    FontMetrics fm = SwingUtilities2.getFontMetrics(testee, av.getFont());
+    int labelWidth = fm.stringWidth("00000"); // width of 3 digits and 2 spaces
+    assertEquals(labelWidth, 45); // note this is not 5 * charWidth
+
+    /*
+     * width 400 pixels leaves (400 - 2*labelWidth) for residue columns
+     * take the whole multiple of character widths
+     */
+    int canvasWidth = 400;
+    int canvasHeight = 300;
+    int residueColumns = (canvasWidth - 2 * labelWidth) / charWidth;
+    int wrappedWidth = testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+    assertEquals(wrappedWidth, residueColumns);
+    assertEquals(PA.getValue(testee, "labelWidthWest"), labelWidth);
+    assertEquals(PA.getValue(testee, "labelWidthEast"), labelWidth);
+    assertEquals(PA.getValue(testee, "wrappedSpaceAboveAlignment"),
+            2 * charHeight);
+    int repeatingHeight = (int) PA.getValue(testee, "wrappedRepeatHeightPx");
+    assertEquals(repeatingHeight, charHeight * (2 + al.getHeight()));
+    assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1);
+
+    /*
+     * repeat height is 17 * (2 + 15) = 289
+     * make canvas height 2 * 289 + 3 * charHeight so just enough to
+     * draw 2 widths and the first sequence of a third
+     */
+    canvasHeight = charHeight * (17 * 2 + 3);
+    testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+    assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
+
+    /*
+     * reduce canvas height by 1 pixel - should not be enough height
+     * to draw 3 widths
+     */
+    canvasHeight -= 1;
+    testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+    assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
+
+    /*
+     * turn off scale above - can now fit in 2 and a bit widths
+     */
+    av.setScaleAboveWrapped(false);
+    testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+    assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 3);
+
+    /*
+     * reduce height to enough for 2 widths and not quite a third
+     * i.e. two repeating heights + spacer + sequence - 1 pixel
+     */
+    canvasHeight = charHeight * (16 * 2 + 2) - 1;
+    testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+    assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 2);
+
+    /*
+     * make canvas width enough for scales and 20 residues
+     */
+    canvasWidth = 2 * labelWidth + 20 * charWidth;
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 20);
+
+    /*
+     * reduce width by 1 pixel - rounds down to 19 residues
+     */
+    canvasWidth -= 1;
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 19);
+
+    /*
+     * turn off West scale - adds labelWidth (45) to available for residues
+     * which with the 11 remainder makes 56 which is 4 more charWidths rem 8
+     */
+    av.setScaleLeftWrapped(false);
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 23);
+
+    /*
+     * add 4 pixels to width to fit in another whole residue column
+     */
+    canvasWidth += 3;
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 23);
+    canvasWidth += 1;
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 24);
+
+    /*
+     * turn off East scale to gain 45 more pixels (3 columns remainder 9)
+     */
+    av.setScaleRightWrapped(false);
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 27);
+
+    /*
+     * add 3 pixels to width to gain a residue column
+     */
+    canvasWidth += 3;
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 28);
+
+    /*
+     * now West but not East scale - lose 45 pixels or 4 columns
+     */
+    av.setScaleLeftWrapped(true);
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 24);
+
+    /*
+     * adding 9 pixels to width regains one column
+     */
+    canvasWidth += 8;
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 24);
+    canvasWidth += 1;
+    wrappedWidth = testee.calculateWrappedGeometry(canvasWidth,
+            canvasHeight);
+    assertEquals(wrappedWidth, 25);
+
+    /*
+     * turn off scales left and right, make width exactly 157 columns
+     */
+    av.setScaleLeftWrapped(false);
+    canvasWidth = al.getWidth() * charWidth;
+    testee.calculateWrappedGeometry(canvasWidth, canvasHeight);
+    assertEquals(PA.getValue(testee, "wrappedVisibleWidths"), 1);
+  }
+}