JAL-3386 correct wrapped width calculation for headless image export
[jalview.git] / src / jalview / gui / AlignmentPanel.java
index ab57b0f..ea8fcdc 100644 (file)
@@ -37,7 +37,6 @@ import jalview.schemes.ResidueProperties;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.Comparison;
 import jalview.util.MessageManager;
-import jalview.util.Platform;
 import jalview.viewmodel.ViewportListenerI;
 import jalview.viewmodel.ViewportRanges;
 
@@ -48,7 +47,7 @@ import java.awt.Dimension;
 import java.awt.Font;
 import java.awt.FontMetrics;
 import java.awt.Graphics;
-import java.awt.Insets;
+import java.awt.Graphics2D;
 import java.awt.event.AdjustmentEvent;
 import java.awt.event.AdjustmentListener;
 import java.awt.event.ComponentAdapter;
@@ -76,8 +75,6 @@ public class AlignmentPanel extends GAlignmentPanel implements
 {
   public AlignViewport av;
 
-  ViewportRanges vpRanges;
-
   OverviewPanel overviewPanel;
 
   private SeqPanel seqPanel;
@@ -97,9 +94,6 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
   private AnnotationLabels alabels;
 
-  // this value is set false when selection area being dragged
-  boolean fastPaint = true;
-
   private int hextent = 0;
 
   private int vextent = 0;
@@ -124,7 +118,6 @@ public class AlignmentPanel extends GAlignmentPanel implements
   {
     alignFrame = af;
     this.av = av;
-    vpRanges = av.getRanges();
     setSeqPanel(new SeqPanel(av, this));
     setIdPanel(new IdPanel(av, this));
 
@@ -156,11 +149,12 @@ public class AlignmentPanel extends GAlignmentPanel implements
         // reset the viewport ranges when the alignment panel is resized
         // in particular, this initialises the end residue value when Jalview
         // is initialised
+        ViewportRanges ranges = av.getRanges();
         if (av.getWrapAlignment())
         {
           int widthInRes = getSeqPanel().seqCanvas.getWrappedCanvasWidth(
                   getSeqPanel().seqCanvas.getWidth());
-          vpRanges.setViewportWidth(widthInRes);
+          ranges.setViewportWidth(widthInRes);
         }
         else
         {
@@ -169,8 +163,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
           int heightInSeq = getSeqPanel().seqCanvas.getHeight()
                   / av.getCharHeight();
 
-          vpRanges.setViewportWidth(widthInRes);
-          vpRanges.setViewportHeight(heightInSeq);
+          ranges.setViewportWidth(widthInRes);
+          ranges.setViewportHeight(heightInSeq);
         }
       }
 
@@ -214,7 +208,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
     alignFrame.updateEditMenuBar();
 
-    paintAlignment(true);
+    // no idea if we need to update structure
+    paintAlignment(true, true);
 
   }
 
@@ -231,7 +226,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
             new Dimension(10, av.getCharHeight() + fm.getDescent()));
     idSpaceFillerPanel1.setPreferredSize(
             new Dimension(10, av.getCharHeight() + fm.getDescent()));
-
+    idwidthAdjuster.invalidate();
+    scalePanelHolder.invalidate();
     getIdPanel().getIdCanvas().gg = null;
     getSeqPanel().seqCanvas.img = null;
     getAnnotationPanel().adjustPanelHeight();
@@ -242,11 +238,6 @@ public class AlignmentPanel extends GAlignmentPanel implements
     getIdPanel().getIdCanvas().setPreferredSize(d);
     hscrollFillerPanel.setPreferredSize(d);
 
-    if (this.alignFrame.getSplitViewContainer() != null)
-    {
-      ((SplitFrame) this.alignFrame.getSplitViewContainer()).adjustLayout();
-    }
-
     repaint();
   }
 
@@ -337,12 +328,12 @@ public class AlignmentPanel extends GAlignmentPanel implements
   }
 
   /**
-   * Highlight the given results on the alignment.
+   * Highlight the given results on the alignment
    * 
    */
   public void highlightSearchResults(SearchResultsI results)
   {
-    boolean scrolled = scrollToPosition(results, 0, true, false);
+    boolean scrolled = scrollToPosition(results, 0, false);
 
     boolean noFastPaint = scrolled && av.getWrapAlignment();
 
@@ -354,13 +345,11 @@ public class AlignmentPanel extends GAlignmentPanel implements
    * (if any)
    * 
    * @param searchResults
-   * @param redrawOverview
    * @return
    */
-  public boolean scrollToPosition(SearchResultsI searchResults,
-          boolean redrawOverview)
+  public boolean scrollToPosition(SearchResultsI searchResults)
   {
-    return scrollToPosition(searchResults, 0, redrawOverview, false);
+    return scrollToPosition(searchResults, 0, false);
   }
 
   /**
@@ -373,16 +362,15 @@ public class AlignmentPanel extends GAlignmentPanel implements
    * @param verticalOffset
    *          if greater than zero, allows scrolling to a position below the
    *          first displayed sequence
-   * @param redrawOverview
-   *          - when set, the overview will be recalculated (takes longer)
    * @param centre
    *          if true, try to centre the search results horizontally in the view
    * @return
    */
   protected boolean scrollToPosition(SearchResultsI results,
-          int verticalOffset, boolean redrawOverview, boolean centre)
+          int verticalOffset, boolean centre)
   {
     int startv, endv, starts, ends;
+    ViewportRanges ranges = av.getRanges();
 
     if (results == null || results.isEmpty() || av == null
             || av.getAlignment() == null)
@@ -410,7 +398,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
      */
     if (centre)
     {
-      int offset = (vpRanges.getEndRes() - vpRanges.getStartRes() + 1) / 2 - 1;
+      int offset = (ranges.getEndRes() - ranges.getStartRes() + 1) / 2 - 1;
       start = Math.max(start - offset, 0);
       end = end + offset - 1;
     }
@@ -426,8 +414,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
     if (av.hasHiddenColumns())
     {
       HiddenColumns hidden = av.getAlignment().getHiddenColumns();
-      start = hidden.findColumnPosition(start);
-      end = hidden.findColumnPosition(end);
+      start = hidden.absoluteToVisibleColumn(start);
+      end = hidden.absoluteToVisibleColumn(end);
       if (start == end)
       {
         if (!hidden.isVisible(r[0]))
@@ -446,33 +434,33 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
     if (!av.getWrapAlignment())
     {
-      if ((startv = vpRanges.getStartRes()) >= start)
+      if ((startv = ranges.getStartRes()) >= start)
       {
         /*
          * Scroll left to make start of search results visible
          */
         setScrollValues(start, seqIndex);
       }
-      else if ((endv = vpRanges.getEndRes()) <= end)
+      else if ((endv = ranges.getEndRes()) <= end)
       {
         /*
          * Scroll right to make end of search results visible
          */
         setScrollValues(startv + end - endv, seqIndex);
       }
-      else if ((starts = vpRanges.getStartSeq()) > seqIndex)
+      else if ((starts = ranges.getStartSeq()) > seqIndex)
       {
         /*
          * Scroll up to make start of search results visible
          */
-        setScrollValues(vpRanges.getStartRes(), seqIndex);
+        setScrollValues(ranges.getStartRes(), seqIndex);
       }
-      else if ((ends = vpRanges.getEndSeq()) <= seqIndex)
+      else if ((ends = ranges.getEndSeq()) <= seqIndex)
       {
         /*
          * Scroll down to make end of search results visible
          */
-        setScrollValues(vpRanges.getStartRes(), starts + seqIndex - ends
+        setScrollValues(ranges.getStartRes(), starts + seqIndex - ends
                 + 1);
       }
       /*
@@ -482,10 +470,10 @@ public class AlignmentPanel extends GAlignmentPanel implements
     }
     else
     {
-      scrollNeeded = vpRanges.scrollToWrappedVisible(start);
+      scrollNeeded = ranges.scrollToWrappedVisible(start);
     }
 
-    paintAlignment(redrawOverview);
+    paintAlignment(false, false);
 
     return scrollNeeded;
   }
@@ -542,7 +530,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
     }
     validateAnnotationDimensions(true);
     addNotify();
-    paintAlignment(true);
+    // TODO: many places call this method and also paintAlignment with various
+    // different settings. this means multiple redraws are triggered...
+    paintAlignment(true, av.needToUpdateStructureViews());
   }
 
   /**
@@ -552,40 +542,10 @@ public class AlignmentPanel extends GAlignmentPanel implements
   protected void validateAnnotationDimensions(boolean adjustPanelHeight)
   {
     int annotationHeight = getAnnotationPanel().adjustPanelHeight();
+    annotationHeight = getAnnotationPanel()
+            .adjustForAlignFrame(adjustPanelHeight, annotationHeight);
 
-    if (adjustPanelHeight)
-    {
-      int rowHeight = av.getCharHeight();
-      int alignmentHeight = rowHeight * av.getAlignment().getHeight();
-
-      /*
-       * Estimate available height in the AlignFrame for alignment +
-       * annotations. Deduct an estimate for title bar, menu bar, scale panel,
-       * hscroll, status bar (as these are not laid out we can't inspect their
-       * actual heights). Insets gives frame borders.
-       */
-      int stuff = Platform.isAMac() ? 80 : 100;
-      Insets insets = alignFrame.getInsets();
-      int availableHeight = alignFrame.getHeight() - stuff - insets.top
-              - insets.bottom;
-
-      /*
-       * If not enough vertical space, maximize annotation height while keeping
-       * at least two rows of alignment visible
-       */
-      if (annotationHeight + alignmentHeight > availableHeight)
-      {
-        annotationHeight = Math.min(annotationHeight,
-                availableHeight - 2 * rowHeight);
-      }
-    }
-    else
-    {
-      // maintain same window layout whilst updating sliders
-      annotationHeight = annotationScroller.getSize().height;
-    }
     hscroll.addNotify();
-
     annotationScroller.setPreferredSize(
             new Dimension(annotationScroller.getWidth(), annotationHeight));
 
@@ -609,7 +569,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
     fontChanged();
     setAnnotationVisible(av.isShowAnnotation());
     boolean wrap = av.getWrapAlignment();
-    vpRanges.setStartSeq(0);
+    ViewportRanges ranges = av.getRanges();
+    ranges.setStartSeq(0);
     scalePanelHolder.setVisible(!wrap);
     hscroll.setVisible(!wrap);
     idwidthAdjuster.setVisible(!wrap);
@@ -623,6 +584,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     {
       annotationScroller.setVisible(true);
       annotationSpaceFillerHolder.setVisible(true);
+      validateAnnotationDimensions(false);
     }
 
     int canvasWidth = getSeqPanel().seqCanvas.getWidth();
@@ -632,16 +594,16 @@ public class AlignmentPanel extends GAlignmentPanel implements
       {
         int widthInRes = getSeqPanel().seqCanvas
                 .getWrappedCanvasWidth(canvasWidth);
-        vpRanges.setViewportWidth(widthInRes);
+        ranges.setViewportWidth(widthInRes);
       }
       else
       {
-        int widthInRes = (canvasWidth / av.getCharWidth()) - 1;
+        int widthInRes = (canvasWidth / av.getCharWidth());
         int heightInSeq = (getSeqPanel().seqCanvas.getHeight()
-                / av.getCharHeight()) - 1;
+                / av.getCharHeight());
 
-        vpRanges.setViewportWidth(widthInRes);
-        vpRanges.setViewportHeight(heightInSeq);
+        ranges.setViewportWidth(widthInRes);
+        ranges.setViewportHeight(heightInSeq);
       }
     }
 
@@ -675,16 +637,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
     }
     else
     {
-      int width = av.getAlignment().getWidth();
+      int width = av.getAlignment().getVisibleWidth();
       int height = av.getAlignment().getHeight();
 
-      if (av.hasHiddenColumns())
-      {
-        // reset the width to exclude hidden columns
-        width = av.getAlignment().getHiddenColumns()
-                .findColumnPosition(width);
-      }
-
       hextent = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
       vextent = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight();
 
@@ -740,10 +695,12 @@ public class AlignmentPanel extends GAlignmentPanel implements
       return;
     }
 
+    ViewportRanges ranges = av.getRanges();
+
     if (evt.getSource() == hscroll)
     {
-      int oldX = vpRanges.getStartRes();
-      int oldwidth = vpRanges.getViewportWidth();
+      int oldX = ranges.getStartRes();
+      int oldwidth = ranges.getViewportWidth();
       int x = hscroll.getValue();
       int width = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
 
@@ -754,12 +711,12 @@ public class AlignmentPanel extends GAlignmentPanel implements
       {
         return;
       }
-      vpRanges.setViewportStartAndWidth(x, width);
+      ranges.setViewportStartAndWidth(x, width);
     }
     else if (evt.getSource() == vscroll)
     {
-      int oldY = vpRanges.getStartSeq();
-      int oldheight = vpRanges.getViewportHeight();
+      int oldY = ranges.getStartSeq();
+      int oldheight = ranges.getViewportHeight();
       int y = vscroll.getValue();
       int height = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight();
 
@@ -770,12 +727,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
       {
         return;
       }
-      vpRanges.setViewportStartAndHeight(y, height);
-    }
-    if (!fastPaint)
-    {
-      repaint();
+      ranges.setViewportStartAndHeight(y, height);
     }
+    repaint();
   }
 
   /**
@@ -790,6 +744,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
     {
       return; // no horizontal scroll when wrapped
     }
+    final ViewportRanges ranges = av.getRanges();
+
     if (evt.getSource() == vscroll)
     {
       int newY = vscroll.getValue();
@@ -799,8 +755,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
        * this prevents infinite recursion of events when the scroll/viewport
        * ranges values are the same
        */
-      int oldX = vpRanges.getStartRes();
-      int oldY = vpRanges.getWrappedScrollPosition(oldX);
+      int oldX = ranges.getStartRes();
+      int oldY = ranges.getWrappedScrollPosition(oldX);
       if (oldY == newY)
       {
         return;
@@ -810,9 +766,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
         /*
          * limit page up/down to one width's worth of positions
          */
-        int rowSize = vpRanges.getViewportWidth();
+        int rowSize = ranges.getViewportWidth();
         int newX = newY > oldY ? oldX + rowSize : oldX - rowSize;
-        vpRanges.setViewportStartAndWidth(Math.max(0, newX), rowSize);
+        ranges.setViewportStartAndWidth(Math.max(0, newX), rowSize);
       }
     }
     else
@@ -833,20 +789,20 @@ public class AlignmentPanel extends GAlignmentPanel implements
                   "Unexpected path through code: Wrapped jar file opened with wrap alignment set in preferences");
 
           // scroll to start of panel
-          vpRanges.setStartRes(0);
-          vpRanges.setStartSeq(0);
+          ranges.setStartRes(0);
+          ranges.setStartSeq(0);
         }
       });
     }
     repaint();
   }
 
-  /**
-   * Repaint the alignment including the annotations and overview panels (if
-   * shown).
+  /* (non-Javadoc)
+   * @see jalview.api.AlignmentViewPanel#paintAlignment(boolean)
    */
   @Override
-  public void paintAlignment(boolean updateOverview)
+  public void paintAlignment(boolean updateOverview,
+          boolean updateStructures)
   {
     final AnnotationSorter sorter = new AnnotationSorter(getAlignment(),
             av.isShowAutocalculatedAbove());
@@ -854,10 +810,12 @@ public class AlignmentPanel extends GAlignmentPanel implements
             av.getSortAnnotationsBy());
     repaint();
 
-    if (updateOverview)
+    if (updateStructures)
     {
-      // TODO: determine if this paintAlignment changed structure colours
       av.getStructureSelectionManager().sequenceColoursChanged(this);
+    }
+    if (updateOverview)
+    {
 
       if (overviewPanel != null)
       {
@@ -875,17 +833,20 @@ public class AlignmentPanel extends GAlignmentPanel implements
   @Override
   public void paintComponent(Graphics g)
   {
-    invalidate();
+    invalidate(); // needed so that the id width adjuster works correctly
 
     Dimension d = getIdPanel().getIdCanvas().getPreferredSize();
     idPanelHolder.setPreferredSize(d);
     hscrollFillerPanel.setPreferredSize(new Dimension(d.width, 12));
-    validate();
+
+    validate(); // needed so that the id width adjuster works correctly
 
     /*
-     * set scroll bar positions
+     * set scroll bar positions - tried to remove but necessary for split panel to resize correctly
+     * though I still think this call should be elsewhere.
      */
-    setScrollValues(vpRanges.getStartRes(), vpRanges.getStartSeq());
+    ViewportRanges ranges = av.getRanges();
+    setScrollValues(ranges.getStartRes(), ranges.getStartSeq());
   }
 
   /**
@@ -897,8 +858,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
    */
   private void setScrollingForWrappedPanel(int topLeftColumn)
   {
-    int scrollPosition = vpRanges.getWrappedScrollPosition(topLeftColumn);
-    int maxScroll = vpRanges.getWrappedMaxScroll(topLeftColumn);
+    ViewportRanges ranges = av.getRanges();
+    int scrollPosition = ranges.getWrappedScrollPosition(topLeftColumn);
+    int maxScroll = ranges.getWrappedMaxScroll(topLeftColumn);
 
     /*
      * a scrollbar's value can be set to at most (maximum-extent)
@@ -943,30 +905,16 @@ public class AlignmentPanel extends GAlignmentPanel implements
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param pg
-   *          DOCUMENT ME!
-   * @param pwidth
-   *          DOCUMENT ME!
-   * @param pheight
-   *          DOCUMENT ME!
-   * @param pi
-   *          DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   * 
-   * @throws PrinterException
-   *           DOCUMENT ME!
-   */
-  /**
    * Draws the alignment image, including sequence ids, sequences, and
    * annotation labels and annotations if shown, on either one or two Graphics
-   * context.
+   * contexts.
    * 
    * @param pageWidth
+   *          in pixels
    * @param pageHeight
-   * @param pi
+   *          in pixels
+   * @param pageIndex
+   *          (0, 1, ...)
    * @param idGraphics
    *          the graphics context for sequence ids and annotation labels
    * @param alignmentGraphics
@@ -975,7 +923,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
    * @return
    * @throws PrinterException
    */
-  public int printUnwrapped(int pageWidth, int pageHeight, int pi,
+  public int printUnwrapped(int pageWidth, int pageHeight, int pageIndex,
           Graphics idGraphics, Graphics alignmentGraphics)
           throws PrinterException
   {
@@ -989,8 +937,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
             : idWidth;
 
     FontMetrics fm = getFontMetrics(av.getFont());
-    int charHeight = av.getCharHeight();
-    int scaleHeight = charHeight + fm.getDescent();
+    final int charHeight = av.getCharHeight();
+    final int scaleHeight = charHeight + fm.getDescent();
 
     idGraphics.setColor(Color.white);
     idGraphics.fillRect(0, 0, pageWidth, pageHeight);
@@ -999,29 +947,20 @@ public class AlignmentPanel extends GAlignmentPanel implements
     /*
      * How many sequences and residues can we fit on a printable page?
      */
-    int totalRes = (pageWidth - idWidth) / av.getCharWidth();
+    final int totalRes = (pageWidth - idWidth) / av.getCharWidth();
 
-    int totalSeq = (pageHeight - scaleHeight) / charHeight - 1;
+    final int totalSeq = (pageHeight - scaleHeight) / charHeight - 1;
 
-    int alignmentWidth = av.getAlignment().getWidth();
+    final int alignmentWidth = av.getAlignment().getVisibleWidth();
     int pagesWide = (alignmentWidth / totalRes) + 1;
 
-    final int startRes = (pi % pagesWide) * totalRes;
-    int endRes = (startRes + totalRes) - 1;
-
-    if (endRes > (alignmentWidth - 1))
-    {
-      endRes = alignmentWidth - 1;
-    }
-
-    final int startSeq = (pi / pagesWide) * totalSeq;
-    int endSeq = startSeq + totalSeq;
+    final int startRes = (pageIndex % pagesWide) * totalRes;
+    final int endRes = Math.min(startRes + totalRes - 1,
+            alignmentWidth - 1);
 
-    int alignmentHeight = av.getAlignment().getHeight();
-    if (endSeq > alignmentHeight)
-    {
-      endSeq = alignmentHeight;
-    }
+    final int startSeq = (pageIndex / pagesWide) * totalSeq;
+    final int alignmentHeight = av.getAlignment().getHeight();
+    final int endSeq = Math.min(startSeq + totalSeq, alignmentHeight);
 
     int pagesHigh = ((alignmentHeight / totalSeq) + 1) * pageHeight;
 
@@ -1032,7 +971,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
     pagesHigh /= pageHeight;
 
-    if (pi >= (pagesWide * pagesHigh))
+    if (pageIndex >= (pagesWide * pagesHigh))
     {
       return Printable.NO_SUCH_PAGE;
     }
@@ -1051,47 +990,12 @@ public class AlignmentPanel extends GAlignmentPanel implements
      * then reset to top left (0, 0)
      */
     idGraphics.translate(0, scaleHeight);
-    idGraphics.setFont(getIdPanel().getIdCanvas().getIdfont());
-    Color currentColor = null;
-    Color currentTextColor = null;
-
-    SequenceI seq;
-    for (int i = startSeq; i < endSeq; i++)
-    {
-      seq = av.getAlignment().getSequenceAt(i);
-      if ((av.getSelectionGroup() != null)
-              && av.getSelectionGroup().getSequences(null).contains(seq))
-      {
-        /*
-         * gray out ids of sequences in selection group (if any)
-         */
-        currentColor = Color.gray;
-        currentTextColor = Color.black;
-      }
-      else
-      {
-        currentColor = av.getSequenceColour(seq);
-        currentTextColor = Color.black;
-      }
+    IdCanvas idCanvas = getIdPanel().getIdCanvas();
+    List<SequenceI> selection = av.getSelectionGroup() == null ? null
+            : av.getSelectionGroup().getSequences(null);
+    idCanvas.drawIds((Graphics2D) idGraphics, av, startSeq, endSeq - 1,
+            selection);
 
-      idGraphics.setColor(currentColor);
-      idGraphics.fillRect(0, (i - startSeq) * charHeight, idWidth,
-              charHeight);
-
-      idGraphics.setColor(currentTextColor);
-
-      int xPos = 0;
-      String displayId = seq.getDisplayId(av.getShowJVSuffix());
-      if (av.isRightAlignIds())
-      {
-        fm = idGraphics.getFontMetrics();
-        xPos = idWidth - fm.stringWidth(displayId) - 4;
-      }
-
-      idGraphics.drawString(displayId, xPos,
-              (((i - startSeq) * charHeight) + charHeight)
-                      - (charHeight / 5));
-    }
     idGraphics.setFont(av.getFont());
     idGraphics.translate(0, -scaleHeight);
 
@@ -1101,7 +1005,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
      */
     alignmentGraphics.translate(alignmentGraphicsOffset, scaleHeight);
     getSeqPanel().seqCanvas.drawPanelForPrinting(alignmentGraphics, startRes,
-            endRes, startSeq, endSeq);
+            endRes, startSeq, endSeq - 1);
     alignmentGraphics.translate(-alignmentGraphicsOffset, 0);
 
     if (av.isShowAnnotation() && (endSeq == alignmentHeight))
@@ -1131,31 +1035,29 @@ public class AlignmentPanel extends GAlignmentPanel implements
   }
 
   /**
-   * DOCUMENT ME!
+   * Prints one page of an alignment in wrapped mode. Returns
+   * Printable.PAGE_EXISTS (0) if a page was drawn, or Printable.NO_SUCH_PAGE if
+   * no page could be drawn (page number out of range).
    * 
-   * @param pg
-   *          DOCUMENT ME!
-   * @param pwidth
-   *          DOCUMENT ME!
-   * @param pheight
-   *          DOCUMENT ME!
-   * @param pi
-   *          DOCUMENT ME!
+   * @param pageWidth
+   * @param pageHeight
+   * @param pageNumber
+   *          (0, 1, ...)
+   * @param g
    * 
-   * @return DOCUMENT ME!
+   * @return
    * 
    * @throws PrinterException
-   *           DOCUMENT ME!
    */
-  public int printWrappedAlignment(int pwidth, int pheight, int pi,
-          Graphics pg) throws PrinterException
+  public int printWrappedAlignment(int pageWidth, int pageHeight, int pageNumber,
+          Graphics g) throws PrinterException
   {
+    getSeqPanel().seqCanvas.calculateWrappedGeometry(getWidth(),
+            getHeight());
     int annotationHeight = 0;
-    AnnotationLabels labels = null;
     if (av.isShowAnnotation())
     {
       annotationHeight = getAnnotationPanel().adjustPanelHeight();
-      labels = new AnnotationLabels(av);
     }
 
     int hgap = av.getCharHeight();
@@ -1169,72 +1071,43 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
     int idWidth = getVisibleIdWidth(false);
 
-    int maxwidth = av.getAlignment().getWidth();
-    if (av.hasHiddenColumns())
-    {
-      maxwidth = av.getAlignment().getHiddenColumns()
-              .findColumnPosition(maxwidth) - 1;
-    }
+    int maxwidth = av.getAlignment().getVisibleWidth();
 
     int resWidth = getSeqPanel().seqCanvas
-            .getWrappedCanvasWidth(pwidth - idWidth);
+            .getWrappedCanvasWidth(pageWidth - idWidth);
+    av.getRanges().setViewportStartAndWidth(0, resWidth);
 
     int totalHeight = cHeight * (maxwidth / resWidth + 1);
 
-    pg.setColor(Color.white);
-    pg.fillRect(0, 0, pwidth, pheight);
-    pg.setFont(av.getFont());
-
-    // //////////////
-    // Draw the ids
-    pg.setColor(Color.black);
+    g.setColor(Color.white);
+    g.fillRect(0, 0, pageWidth, pageHeight);
+    g.setFont(av.getFont());
+    g.setColor(Color.black);
 
-    pg.translate(0, -pi * pheight);
-
-    pg.setClip(0, pi * pheight, pwidth, pheight);
-
-    int ypos = hgap;
-
-    do
-    {
-      for (int i = 0; i < av.getAlignment().getHeight(); i++)
-      {
-        pg.setFont(getIdPanel().getIdCanvas().getIdfont());
-        SequenceI s = av.getAlignment().getSequenceAt(i);
-        String string = s.getDisplayId(av.getShowJVSuffix());
-        int xPos = 0;
-        if (av.isRightAlignIds())
-        {
-          FontMetrics fm = pg.getFontMetrics();
-          xPos = idWidth - fm.stringWidth(string) - 4;
-        }
-        pg.drawString(string, xPos,
-                ((i * av.getCharHeight()) + ypos + av.getCharHeight())
-                        - (av.getCharHeight() / 5));
-      }
-      if (labels != null)
-      {
-        pg.translate(-3, ypos
-                + (av.getAlignment().getHeight() * av.getCharHeight()));
+    /*
+     * method: print the whole wrapped alignment, but with a clip region that
+     * is restricted to the requested page; this supports selective print of 
+     * single pages or ranges, (at the cost of repeated processing in the 
+     * 'normal' case, when all pages are printed)
+     */
+    g.translate(0, -pageNumber * pageHeight);
 
-        pg.setFont(av.getFont());
-        labels.drawComponent(pg, idWidth);
-        pg.translate(+3, -ypos
-                - (av.getAlignment().getHeight() * av.getCharHeight()));
-      }
+    g.setClip(0, pageNumber * pageHeight, pageWidth, pageHeight);
 
-      ypos += cHeight;
-    } while (ypos < totalHeight);
+    /*
+     * draw sequence ids and annotation labels (if shown)
+     */
+    IdCanvas idCanvas = getIdPanel().getIdCanvas();
+    idCanvas.drawIdsWrapped((Graphics2D) g, av, 0, totalHeight);
 
-    pg.translate(idWidth, 0);
+    g.translate(idWidth, 0);
 
-    getSeqPanel().seqCanvas.drawWrappedPanelForPrinting(pg, pwidth - idWidth,
+    getSeqPanel().seqCanvas.drawWrappedPanelForPrinting(g, pageWidth - idWidth,
             totalHeight, 0);
 
-    if ((pi * pheight) < totalHeight)
+    if ((pageNumber * pageHeight) < totalHeight)
     {
       return Printable.PAGE_EXISTS;
-
     }
     else
     {
@@ -1361,12 +1234,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
   public AlignmentDimension getAlignmentDimension()
   {
-    int maxwidth = av.getAlignment().getWidth();
-    if (av.hasHiddenColumns())
-    {
-      maxwidth = av.getAlignment().getHiddenColumns()
-              .findColumnPosition(maxwidth);
-    }
+    int maxwidth = av.getAlignment().getVisibleWidth();
 
     int height = ((av.getAlignment().getHeight() + 1) * av.getCharHeight())
             + getScalePanel().getHeight();
@@ -1549,6 +1417,12 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
   }
 
+  /**
+   * Answers the height of the entire alignment in pixels, assuming it is in
+   * wrapped mode
+   * 
+   * @return
+   */
   int getWrappedHeight()
   {
     int seqPanelWidth = getSeqPanel().seqCanvas.getWidth();
@@ -1573,6 +1447,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     int annotationHeight = 0;
     if (av.isShowAnnotation())
     {
+      hgap += SeqCanvas.SEQS_ANNOTATION_GAP;
       annotationHeight = getAnnotationPanel().adjustPanelHeight();
     }
 
@@ -1583,7 +1458,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     if (av.hasHiddenColumns())
     {
       maxwidth = av.getAlignment().getHiddenColumns()
-              .findColumnPosition(maxwidth) - 1;
+              .absoluteToVisibleColumn(maxwidth) - 1;
     }
 
     int height = ((maxwidth / chunkWidth) + 1) * cHeight;
@@ -1609,13 +1484,14 @@ public class AlignmentPanel extends GAlignmentPanel implements
     if (annotationPanel != null)
     {
       annotationPanel.dispose();
+      annotationPanel = null;
     }
 
     if (av != null)
     {
       av.removePropertyChangeListener(propertyChangeListener);
-      jalview.structure.StructureSelectionManager ssm = av
-              .getStructureSelectionManager();
+      propertyChangeListener = null;
+      StructureSelectionManager ssm = av.getStructureSelectionManager();
       ssm.removeStructureViewerListener(getSeqPanel(), null);
       ssm.removeSelectionListener(getSeqPanel());
       ssm.removeCommandListener(av);
@@ -1638,9 +1514,15 @@ public class AlignmentPanel extends GAlignmentPanel implements
    */
   protected void closeChildFrames()
   {
+    if (overviewPanel != null)
+    {
+      overviewPanel.dispose();
+      overviewPanel = null;
+    }
     if (calculationDialog != null)
     {
       calculationDialog.closeFrame();
+      calculationDialog = null;
     }
   }
 
@@ -1674,7 +1556,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
   @Override
   public String getViewName()
   {
-    return av.viewName;
+    return av.getViewName();
   }
 
   /**
@@ -1805,36 +1687,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
    */
   protected void scrollToCentre(SearchResultsI sr, int verticalOffset)
   {
-    /*
-     * To avoid jumpy vertical scrolling (if some sequences are gapped or not
-     * mapped), we can make the scroll-to location a sequence above the one
-     * actually mapped.
-     */
-    SequenceI mappedTo = sr.getResults().get(0).getSequence();
-    List<SequenceI> seqs = av.getAlignment().getSequences();
-
-    /*
-     * This is like AlignmentI.findIndex(seq) but here we are matching the
-     * dataset sequence not the aligned sequence
-     */
-    boolean matched = false;
-    for (SequenceI seq : seqs)
-    {
-      if (mappedTo == seq.getDatasetSequence())
-      {
-        matched = true;
-        break;
-      }
-    }
-    if (!matched)
-    {
-      return; // failsafe, shouldn't happen
-    }
-
-    /*
-     * Scroll to position but centring the target residue.
-     */
-    scrollToPosition(sr, verticalOffset, true, true);
+    scrollToPosition(sr, verticalOffset, true);
   }
 
   /**
@@ -1870,7 +1723,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     if (adjustHeight)
     {
       // sort, repaint, update overview
-      paintAlignment(true);
+      paintAlignment(true, false);
     }
     else
     {
@@ -1887,8 +1740,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
   public void propertyChange(PropertyChangeEvent evt)
   {
     // update this panel's scroll values based on the new viewport ranges values
-    int x = vpRanges.getStartRes();
-    int y = vpRanges.getStartSeq();
+    ViewportRanges ranges = av.getRanges();
+    int x = ranges.getStartRes();
+    int y = ranges.getStartSeq();
     setScrollValues(x, y);
 
     // now update any complementary alignment (its viewport ranges object