Merge branch 'bug/JAL-3099alignmentVisibleWidth' into merge/JAL-3099
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 4 Mar 2019 12:30:09 +0000 (12:30 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 4 Mar 2019 12:30:09 +0000 (12:30 +0000)
Conflicts:
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/IdCanvas.java
test/jalview/datamodel/AlignmentTest.java

1  2 
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/AlignmentI.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/IdCanvas.java
src/jalview/gui/ScalePanel.java
src/jalview/gui/SeqCanvas.java
test/jalview/datamodel/AlignmentTest.java

@@@ -720,31 -720,17 +720,17 @@@ public class Alignment implements Align
    
      return maxLength;
    }
-   /*
    @Override
-   public int getWidth()
+   public int getVisibleWidth()
    {
-     final Wrapper temp = new Wrapper();
-   
-     forEachSequence(new Consumer<SequenceI>()
+     int w = getWidth();
+     if (hiddenCols != null)
      {
-       @Override
-       public void accept(SequenceI s)
-       {
-         if (s.getLength() > temp.inner)
-         {
-           temp.inner = s.getLength();
-         }
-       }
-     }, 0, sequences.size() - 1);
-   
-     return temp.inner;
+       w -= hiddenCols.getSize();
+     }
+     return w;
    }
-   
-   public static class Wrapper
-   {
-     public int inner;
-   }*/
  
    /**
     * DOCUMENT ME!
    }
  
    @Override
 -  public void setHiddenColumns(HiddenColumns cols)
 +  public boolean setHiddenColumns(HiddenColumns cols)
    {
 +    boolean changed = cols == null ? hiddenCols != null
 +            : !cols.equals(hiddenCols);
      hiddenCols = cols;
 +    return changed;
    }
  
    @Override
@@@ -48,15 -48,29 +48,29 @@@ public interface AlignmentI extends Ann
  
    /**
     * 
-    * Calculates the maximum width of the alignment, including gaps.
+    * Answers the width of the alignment, including gaps, that is, the length of
+    * the longest sequence, or -1 if there are no sequences. Avoid calling this
+    * method repeatedly where possible, as it has to perform a calculation. Note
+    * that this width includes any hidden columns.
     * 
-    * @return Greatest sequence length within alignment, or -1 if no sequences
-    *         present
+    * @return
+    * @see AlignmentI#getVisibleWidth()
     */
    @Override
    int getWidth();
  
    /**
+    * 
+    * Answers the visible width of the alignment, including gaps, that is, the
+    * length of the longest sequence, excluding any hidden columns. Answers -1 if
+    * there are no sequences. Avoid calling this method repeatedly where
+    * possible, as it has to perform a calculation.
+    * 
+    * @return
+    */
+   int getVisibleWidth();
+   /**
     * Calculates if this set of sequences (visible and invisible) are all the
     * same length
     * 
    AlignedCodonFrame getMapping(SequenceI mapFrom, SequenceI mapTo);
  
    /**
 -   * Set the hidden columns collection on the alignment
 +   * Set the hidden columns collection on the alignment. Answers true if the
 +   * hidden column selection changed, else false.
     * 
     * @param cols
 +   * @return
     */
 -  public void setHiddenColumns(HiddenColumns cols);
 +  public boolean setHiddenColumns(HiddenColumns cols);
  
    /**
     * Set the first sequence as representative and hide its insertions. Typically
@@@ -64,7 -64,6 +64,7 @@@ import jalview.gui.ColourMenuHelper.Col
  import jalview.gui.ViewSelectionMenu.ViewSetProvider;
  import jalview.io.AlignmentProperties;
  import jalview.io.AnnotationFile;
 +import jalview.io.BackupFiles;
  import jalview.io.BioJsHTMLOutput;
  import jalview.io.DataSourceType;
  import jalview.io.FileFormat;
@@@ -734,9 -733,9 +734,9 @@@ public class AlignFrame extends GAlignF
  
      int aSize = alignPanels.size();
  
 -    tabbedPane.setVisible(aSize > 1 || ap.av.viewName != null);
 +    tabbedPane.setVisible(aSize > 1 || ap.av.getViewName() != null);
  
 -    if (aSize == 1 && ap.av.viewName == null)
 +    if (aSize == 1 && ap.av.getViewName() == null)
      {
        this.getContentPane().add(ap, BorderLayout.CENTER);
      }
  
        expandViews.setEnabled(true);
        gatherViews.setEnabled(true);
 -      tabbedPane.addTab(ap.av.viewName, ap);
 +      tabbedPane.addTab(ap.av.getViewName(), ap);
  
        ap.setVisible(false);
      }
      gatherViews.setEnabled(true);
      tabbedPane.setVisible(true);
      AlignmentPanel first = alignPanels.get(0);
 -    tabbedPane.addTab(first.av.viewName, first);
 +    tabbedPane.addTab(first.av.getViewName(), first);
      this.getContentPane().add(tabbedPane, BorderLayout.CENTER);
    }
  
     * @param av
     *          AlignViewport
     */
 -  void setMenusFromViewport(AlignViewport av)
 +  public void setMenusFromViewport(AlignViewport av)
    {
      padGapsMenuitem.setSelected(av.isPadGaps());
      colourTextMenuItem.setSelected(av.isShowColourText());
      return progressBar.operationInProgress();
    }
  
 +  /**
 +   * Sets the text of the status bar. Note that setting a null or empty value
 +   * will cause the status bar to be hidden, with possibly undesirable flicker
 +   * of the screen layout.
 +   */
    @Override
    public void setStatus(String text)
    {
                  shortName.lastIndexOf(java.io.File.separatorChar) + 1);
        }
  
 -      success = new Jalview2XML().saveAlignment(this, file, shortName);
 +      success = new jalview.project.Jalview2XML().saveAlignment(this, file,
 +              shortName);
  
        statusBar.setText(MessageManager.formatMessage(
                "label.successfully_saved_to_file_in_format", new Object[]
        }
        else
        {
 +        // create backupfiles object and get new temp filename destination
 +        BackupFiles backupfiles = new BackupFiles(file);
 +
          try
          {
 -          PrintWriter out = new PrintWriter(new FileWriter(file));
 +          PrintWriter out = new PrintWriter(
 +                  new FileWriter(backupfiles.getTempFilePath()));
  
            out.print(output);
            out.close();
            success = false;
            ex.printStackTrace();
          }
 +
 +        backupfiles.setWriteSuccess(success);
 +        // do the backup file roll and rename the temp file to actual file
 +        success = backupfiles.rollBackupsAndRenameTempFile();
 +
        }
      }
  
    @Override
    public void selectAllSequenceMenuItem_actionPerformed(ActionEvent e)
    {
 -    SequenceGroup sg = new SequenceGroup();
 -
 -    for (int i = 0; i < viewport.getAlignment().getSequences().size(); i++)
 -    {
 -      sg.addSequence(viewport.getAlignment().getSequenceAt(i), false);
 -    }
 +    SequenceGroup sg = new SequenceGroup(
 +            viewport.getAlignment().getSequences());
  
      sg.setEndRes(viewport.getAlignment().getWidth() - 1);
      viewport.setSelectionGroup(sg);
 +    viewport.isSelectionGroupChanged(true);
      viewport.sendSelection();
      // JAL-2034 - should delegate to
      // alignPanel to decide if overview needs
      /*
       * Create a new AlignmentPanel (with its own, new Viewport)
       */
 -    AlignmentPanel newap = new Jalview2XML().copyAlignPanel(alignPanel);
 +    AlignmentPanel newap = new jalview.project.Jalview2XML()
 +            .copyAlignPanel(alignPanel);
      if (!copyAnnotation)
      {
        /*
  
      newap.av.setGatherViewsHere(false);
  
 -    if (viewport.viewName == null)
 +    if (viewport.getViewName() == null)
      {
 -      viewport.viewName = MessageManager
 -              .getString("label.view_name_original");
 +      viewport.setViewName(MessageManager
 +              .getString("label.view_name_original"));
      }
  
      /*
        newap.refresh(true); // adjust layout of annotations
      }
  
 -    newap.av.viewName = getNewViewName(viewTitle);
 +    newap.av.setViewName(getNewViewName(viewTitle));
  
      addAlignmentPanel(newap, true);
      newap.alignmentChanged();
        if (comp instanceof AlignmentPanel)
        {
          AlignmentPanel ap = (AlignmentPanel) comp;
 -        if (!existingNames.contains(ap.av.viewName))
 +        if (!existingNames.contains(ap.av.getViewName()))
          {
 -          existingNames.add(ap.av.viewName);
 +          existingNames.add(ap.av.getViewName());
          }
        }
      }
      viewport.setFollowHighlight(state);
      if (state)
      {
 -      alignPanel.scrollToPosition(viewport.getSearchResults(), false);
 +      alignPanel.scrollToPosition(viewport.getSearchResults());
      }
    }
  
      viewport.expandColSelection(sg, false);
      viewport.hideAllSelectedSeqs();
      viewport.hideSelectedColumns();
+     alignPanel.updateLayout();
      alignPanel.paintAlignment(true, true);
      viewport.sendSelection();
    }
    public void hideSelColumns_actionPerformed(ActionEvent e)
    {
      viewport.hideSelectedColumns();
+     alignPanel.updateLayout();
      alignPanel.paintAlignment(true, true);
      viewport.sendSelection();
    }
  
      frameTitle += " from ";
  
 -    if (viewport.viewName != null)
 +    if (viewport.getViewName() != null)
      {
 -      frameTitle += viewport.viewName + " of ";
 +      frameTitle += viewport.getViewName() + " of ";
      }
  
      frameTitle += this.title;
  
        if (reply != null)
        {
 -        viewport.viewName = reply;
 +        viewport.setViewName(reply);
          // TODO warn if reply is in getExistingViewNames()?
          tabbedPane.setTitleAt(tabbedPane.getSelectedIndex(), reply);
        }
    {
      if (avc.createGroup())
      {
 +      if (applyAutoAnnotationSettings.isSelected())
 +      {
 +        alignPanel.updateAnnotation(true, false);
 +      }
        alignPanel.alignmentChanged();
      }
    }
     */
    public List<? extends AlignmentViewPanel> getAlignPanels()
    {
 -    return alignPanels == null ? Arrays.asList(alignPanel) : alignPanels;
 +    // alignPanels is never null
 +    // return alignPanels == null ? Arrays.asList(alignPanel) : alignPanels;
 +    return alignPanels;
    }
  
    /**
@@@ -37,6 -37,7 +37,6 @@@ import jalview.schemes.ResiduePropertie
  import jalview.structure.StructureSelectionManager;
  import jalview.util.Comparison;
  import jalview.util.MessageManager;
 -import jalview.util.Platform;
  import jalview.viewmodel.ViewportListenerI;
  import jalview.viewmodel.ViewportRanges;
  
@@@ -47,7 -48,6 +47,7 @@@ import java.awt.Dimension
  import java.awt.Font;
  import java.awt.FontMetrics;
  import java.awt.Graphics;
 +import java.awt.Graphics2D;
  import java.awt.event.AdjustmentEvent;
  import java.awt.event.AdjustmentListener;
  import java.awt.event.ComponentAdapter;
@@@ -328,12 -328,12 +328,12 @@@ public class AlignmentPanel extends GAl
    }
  
    /**
 -   * 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();
  
     * (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);
    }
  
    /**
     * @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();
        scrollNeeded = ranges.scrollToWrappedVisible(start);
      }
  
 -    paintAlignment(redrawOverview, false);
 +    paintAlignment(false, false);
  
      return scrollNeeded;
    }
    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, insets. 
 -       */
 -      int stuff = Platform.isAMac() ? 120 : 140;
 -      int availableHeight = alignFrame.getHeight() - stuff;
 -
 -      /*
 -       * 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));
  
      {
        annotationScroller.setVisible(true);
        annotationSpaceFillerHolder.setVisible(true);
 +      validateAnnotationDimensions(false);
      }
  
      int canvasWidth = getSeqPanel().seqCanvas.getWidth();
      }
      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()
-                 .absoluteToVisibleColumn(width);
-       }
        hextent = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
        vextent = getSeqPanel().seqCanvas.getHeight() / av.getCharHeight();
  
    }
  
    /**
 -   * 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
     * @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
    {
              : 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);
      /*
       * 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;
  
-     final int alignmentWidth = av.getAlignment().getWidth();
-     final int pagesWide = (alignmentWidth / totalRes) + 1;
 -    int alignmentWidth = av.getAlignment().getVisibleWidth();
++    final int alignmentWidth = av.getAlignment().getVisibleWidth();
+     int pagesWide = (alignmentWidth / totalRes) + 1;
  
 -    final int startRes = (pi % pagesWide) * totalRes;
 -    int endRes = (startRes + totalRes) - 1;
 +    final int startRes = (pageIndex % pagesWide) * totalRes;
 +    final int endRes = Math.min(startRes + totalRes - 1,
 +            alignmentWidth - 1);
  
 -    if (endRes > (alignmentWidth - 1))
 -    {
 -      endRes = alignmentWidth - 1;
 -    }
 -
 -    final int startSeq = (pi / pagesWide) * totalSeq;
 -    int endSeq = startSeq + totalSeq;
 -
 -    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;
  
  
      pagesHigh /= pageHeight;
  
 -    if (pi >= (pagesWide * pagesHigh))
 +    if (pageIndex >= (pagesWide * pagesHigh))
      {
        return Printable.NO_SUCH_PAGE;
      }
       * 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;
 -      }
 -
 -      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;
 -      }
 +    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.drawString(displayId, xPos,
 -              (((i - startSeq) * charHeight) + charHeight)
 -                      - (charHeight / 5));
 -    }
      idGraphics.setFont(av.getFont());
      idGraphics.translate(0, -scaleHeight);
  
       */
      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))
    }
  
    /**
 -   * 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
    {
      int annotationHeight = 0;
      if (av.isShowAnnotation())
      {
        annotationHeight = getAnnotationPanel().adjustPanelHeight();
 -      labels = new AnnotationLabels(av);
      }
  
      int hgap = av.getCharHeight();
  
      int idWidth = getVisibleIdWidth(false);
  
-     int maxwidth = av.getAlignment().getWidth();
-     if (av.hasHiddenColumns())
-     {
-       maxwidth = av.getAlignment().getHiddenColumns()
-               .absoluteToVisibleColumn(maxwidth) - 1;
-     }
+     int maxwidth = av.getAlignment().getVisibleWidth();
  
      int resWidth = getSeqPanel().seqCanvas
 -            .getWrappedCanvasWidth(pwidth - idWidth);
 +            .getWrappedCanvasWidth(pageWidth - idWidth);
  
      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 some 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
      {
  
    public AlignmentDimension getAlignmentDimension()
    {
-     int maxwidth = av.getAlignment().getWidth();
-     if (av.hasHiddenColumns())
-     {
-       maxwidth = av.getAlignment().getHiddenColumns()
-               .absoluteToVisibleColumn(maxwidth);
-     }
+     int maxwidth = av.getAlignment().getVisibleWidth();
  
      int height = ((av.getAlignment().getHeight() + 1) * av.getCharHeight())
              + getScalePanel().getHeight();
    @Override
    public String getViewName()
    {
 -    return av.viewName;
 +    return av.getViewName();
    }
  
    /**
     */
    protected void scrollToCentre(SearchResultsI sr, int verticalOffset)
    {
 -    scrollToPosition(sr, verticalOffset, true, true);
 +    scrollToPosition(sr, verticalOffset, true);
    }
  
    /**
@@@ -63,6 -63,10 +63,6 @@@ public class IdCanvas extends JPanel im
  
    List<SequenceI> searchResults;
  
 -  FontMetrics fm;
 -
 -  AnnotationLabels labels = null;
 -
    AnnotationPanel ap;
  
    private Font idfont;
@@@ -84,7 -88,7 +84,7 @@@
    /**
     * DOCUMENT ME!
     * 
 -   * @param gg
 +   * @param g
     *          DOCUMENT ME!
     * @param hiddenRows
     *          true - check and display hidden row marker if need be
     * @param ypos
     *          DOCUMENT ME!
     */
 -  public void drawIdString(Graphics2D gg, boolean hiddenRows, SequenceI s,
 +  public void drawIdString(Graphics2D g, boolean hiddenRows, SequenceI s,
            int i, int starty, int ypos)
    {
      int xPos = 0;
  
      if ((searchResults != null) && searchResults.contains(s))
      {
 -      gg.setColor(Color.black);
 -      gg.fillRect(0, ((i - starty) * charHeight) + ypos, getWidth(),
 +      g.setColor(Color.black);
 +      g.fillRect(0, ((i - starty) * charHeight) + ypos, getWidth(),
                charHeight);
 -      gg.setColor(Color.white);
 +      g.setColor(Color.white);
      }
      else if ((av.getSelectionGroup() != null)
              && av.getSelectionGroup().getSequences(null).contains(s))
      {
 -      gg.setColor(Color.lightGray);
 -      gg.fillRect(0, ((i - starty) * charHeight) + ypos, getWidth(),
 +      g.setColor(Color.lightGray);
 +      g.fillRect(0, ((i - starty) * charHeight) + ypos, getWidth(),
                charHeight);
 -      gg.setColor(Color.white);
 +      g.setColor(Color.white);
      }
      else
      {
 -      gg.setColor(av.getSequenceColour(s));
 -      gg.fillRect(0, ((i - starty) * charHeight) + ypos, getWidth(),
 +      g.setColor(av.getSequenceColour(s));
 +      g.fillRect(0, ((i - starty) * charHeight) + ypos, getWidth(),
                charHeight);
 -      gg.setColor(Color.black);
 +      g.setColor(Color.black);
      }
  
      if (av.isRightAlignIds())
      {
 +      FontMetrics fm = g.getFontMetrics();
        xPos = panelWidth
                - fm.stringWidth(s.getDisplayId(av.getShowJVSuffix())) - 4;
      }
  
 -    gg.drawString(s.getDisplayId(av.getShowJVSuffix()), xPos,
 +    g.drawString(s.getDisplayId(av.getShowJVSuffix()), xPos,
              (((i - starty + 1) * charHeight) + ypos) - (charHeight / 5));
  
      if (hiddenRows)
      {
 -      drawMarker(i, starty, ypos);
 +      drawMarker(g, av, i, starty, ypos);
      }
  
    }
  
      gg.translate(0, transY);
  
 -    drawIds(ss, es);
 +    drawIds(gg, av, ss, es, searchResults);
  
      gg.translate(0, -transY);
  
      gg.setColor(Color.white);
      gg.fillRect(0, 0, getWidth(), imgHeight);
      
 -    drawIds(av.getRanges().getStartSeq(), av.getRanges().getEndSeq());
 +    drawIds(gg, av, av.getRanges().getStartSeq(), av.getRanges().getEndSeq(), searchResults);
      
      g.drawImage(image, 0, 0, this);
    }
  
    /**
 -   * DOCUMENT ME!
 +   * Draws sequence ids from sequence index startSeq to endSeq (inclusive), with
 +   * the font and other display settings configured on the viewport. Ids of
 +   * sequences included in the selection are coloured grey, otherwise the
 +   * current id colour for the sequence id is used.
     * 
 -   * @param starty
 -   *          DOCUMENT ME!
 -   * @param endy
 -   *          DOCUMENT ME!
 +   * @param g
 +   * @param alignViewport
 +   * @param startSeq
 +   * @param endSeq
 +   * @param selection
     */
 -  void drawIds(int starty, int endy)
 +  void drawIds(Graphics2D g, AlignViewport alignViewport, final int startSeq,
 +          final int endSeq, List<SequenceI> selection)
    {
 -    if (av.isSeqNameItalics())
 +    Font font = alignViewport.getFont();
 +    if (alignViewport.isSeqNameItalics())
      {
 -      setIdfont(new Font(av.getFont().getName(), Font.ITALIC,
 -              av.getFont().getSize()));
 +      setIdfont(new Font(font.getName(), Font.ITALIC,
 +              font.getSize()));
      }
      else
      {
 -      setIdfont(av.getFont());
 +      setIdfont(font);
      }
  
 -    gg.setFont(getIdfont());
 -    fm = gg.getFontMetrics();
 +    g.setFont(getIdfont());
 +    FontMetrics fm = g.getFontMetrics();
  
 -    if (av.antiAlias)
 +    if (alignViewport.antiAlias)
      {
 -      gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
 +      g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
      }
  
      Color currentColor = Color.white;
      Color currentTextColor = Color.black;
  
 -    boolean hasHiddenRows = av.hasHiddenRows();
 +    boolean hasHiddenRows = alignViewport.hasHiddenRows();
  
 -    if (av.getWrapAlignment())
 +    if (alignViewport.getWrapAlignment())
      {
 -      drawIdsWrapped(starty, hasHiddenRows);
 +      drawIdsWrapped(g, alignViewport, startSeq, getHeight());
        return;
      }
  
 -    // No need to hang on to labels if we're not wrapped
 -    labels = null;
 -
      // Now draw the id strings
      int panelWidth = getWidth();
      int xPos = 0;
  
 -    SequenceI sequence;
      // Now draw the id strings
 -    for (int i = starty; i <= endy; i++)
 +    for (int i = startSeq; i <= endSeq; i++)
      {
 -      sequence = av.getAlignment().getSequenceAt(i);
 +      SequenceI sequence = alignViewport.getAlignment().getSequenceAt(i);
  
        if (sequence == null)
        {
          continue;
        }
  
 -      if (hasHiddenRows || av.isDisplayReferenceSeq())
 +      if (hasHiddenRows || alignViewport.isDisplayReferenceSeq())
        {
 -        setHiddenFont(sequence);
 +        g.setFont(getHiddenFont(sequence, alignViewport));
        }
  
        // Selected sequence colours
 -      if ((searchResults != null) && searchResults.contains(sequence))
 +      if (selection != null && selection.contains(sequence))
        {
          currentColor = Color.black;
          currentTextColor = Color.white;
        }
 -      else if ((av.getSelectionGroup() != null) && av.getSelectionGroup()
 -              .getSequences(null).contains(sequence))
 +      else if ((alignViewport.getSelectionGroup() != null) && alignViewport
 +              .getSelectionGroup().getSequences(null).contains(sequence))
        {
          currentColor = Color.lightGray;
          currentTextColor = Color.black;
        }
        else
        {
 -        currentColor = av.getSequenceColour(sequence);
 +        currentColor = alignViewport.getSequenceColour(sequence);
          currentTextColor = Color.black;
        }
  
 -      gg.setColor(currentColor);
 +      g.setColor(currentColor);
  
 -      gg.fillRect(0, (i - starty) * av.getCharHeight(), getWidth(),
 -              av.getCharHeight());
 +      int charHeight = alignViewport.getCharHeight();
 +      g.fillRect(0, (i - startSeq) * charHeight,
 +              getWidth(), charHeight);
  
 -      gg.setColor(currentTextColor);
 +      g.setColor(currentTextColor);
  
 -      String string = sequence.getDisplayId(av.getShowJVSuffix());
 +      String string = sequence
 +              .getDisplayId(alignViewport.getShowJVSuffix());
  
 -      if (av.isRightAlignIds())
 +      if (alignViewport.isRightAlignIds())
        {
          xPos = panelWidth - fm.stringWidth(string) - 4;
        }
  
 -      gg.drawString(string, xPos,
 -              (((i - starty) * av.getCharHeight()) + av.getCharHeight())
 -                      - (av.getCharHeight() / 5));
 +      g.drawString(string, xPos, (((i - startSeq) * charHeight) + charHeight)
 +              - (charHeight / 5));
  
        if (hasHiddenRows)
        {
 -        drawMarker(i, starty, 0);
 +        drawMarker(g, alignViewport, i, startSeq, 0);
        }
      }
    }
  
    /**
 -   * Draws sequence ids in wrapped mode
 +   * Draws sequence ids, and annotation labels if annotations are shown, in
 +   * wrapped mode
     * 
 -   * @param starty
 -   * @param hasHiddenRows
 +   * @param g
 +   * @param alignViewport
 +   * @param startSeq
     */
 -  protected void drawIdsWrapped(int starty, boolean hasHiddenRows)
 +  void drawIdsWrapped(Graphics2D g, AlignViewport alignViewport,
 +          int startSeq, int pageHeight)
    {
 -    int maxwidth = av.getAlignment().getVisibleWidth();
 -    int alheight = av.getAlignment().getHeight();
 +    int alignmentWidth = alignViewport.getAlignment().getWidth();
 +    final int alheight = alignViewport.getAlignment().getHeight();
  
-     if (alignViewport.hasHiddenColumns())
-     {
-       alignmentWidth = alignViewport.getAlignment().getHiddenColumns()
-               .absoluteToVisibleColumn(alignmentWidth) - 1;
-     }
      int annotationHeight = 0;
  
 -    if (av.isShowAnnotation())
 +    AnnotationLabels labels = null;
 +    if (alignViewport.isShowAnnotation())
      {
        if (ap == null)
        {
 -        ap = new AnnotationPanel(av);
 +        ap = new AnnotationPanel(alignViewport);
        }
 -
        annotationHeight = ap.adjustPanelHeight();
 -      if (labels == null)
 -      {
 -        labels = new AnnotationLabels(av);
 -      }
 +      labels = new AnnotationLabels(alignViewport);
      }
  
 -    int hgap = av.getCharHeight();
 -    if (av.getScaleAboveWrapped())
 +    final int charHeight = alignViewport.getCharHeight();
 +    int hgap = charHeight;
 +    if (alignViewport.getScaleAboveWrapped())
      {
 -      hgap += av.getCharHeight();
 +      hgap += charHeight;
      }
  
 -    int cHeight = alheight * av.getCharHeight() + hgap + annotationHeight;
 +    /*
 +     * height of alignment + gap + annotations (if shown)
 +     */
 +    int cHeight = alheight * charHeight + hgap
 +            + annotationHeight;
  
 -    ViewportRanges ranges = av.getRanges();
 +    ViewportRanges ranges = alignViewport.getRanges();
  
      int rowSize = ranges.getViewportWidth();
  
       * draw repeating sequence ids until out of sequence data or
       * out of visible space, whichever comes first
       */
 +    boolean hasHiddenRows = alignViewport.hasHiddenRows();
      int ypos = hgap;
 -    int row = ranges.getStartRes();
 -    while ((ypos <= getHeight()) && (row < maxwidth))
 +    int rowStartRes = ranges.getStartRes();
 +    while ((ypos <= pageHeight) && (rowStartRes < alignmentWidth))
      {
 -      for (int i = starty; i < alheight; i++)
 +      for (int i = startSeq; i < alheight; i++)
        {
 -        SequenceI s = av.getAlignment().getSequenceAt(i);
 -        if (hasHiddenRows || av.isDisplayReferenceSeq())
 +        SequenceI s = alignViewport.getAlignment().getSequenceAt(i);
 +        if (hasHiddenRows || alignViewport.isDisplayReferenceSeq())
          {
 -          setHiddenFont(s);
 +          g.setFont(getHiddenFont(s, alignViewport));
          }
          else
          {
 -          gg.setFont(getIdfont());
 +          g.setFont(getIdfont());
          }
 -
 -        drawIdString(gg, hasHiddenRows, s, i, 0, ypos);
 +        drawIdString(g, hasHiddenRows, s, i, 0, ypos);
        }
  
 -      if (labels != null && av.isShowAnnotation())
 +      if (labels != null && alignViewport.isShowAnnotation())
        {
 -        gg.translate(0, ypos + (alheight * av.getCharHeight()));
 -        labels.drawComponent(gg, getWidth());
 -        gg.translate(0, -ypos - (alheight * av.getCharHeight()));
 +        g.translate(0, ypos + (alheight * charHeight));
 +        labels.drawComponent(g, getWidth());
 +        g.translate(0, -ypos - (alheight * charHeight));
        }
  
        ypos += cHeight;
 -      row += rowSize;
 +      rowStartRes += rowSize;
      }
    }
  
 -  void drawMarker(int i, int starty, int yoffset)
 +  /**
 +   * Draws a marker (a blue right-pointing triangle) between sequences to
 +   * indicate hidden sequences.
 +   * 
 +   * @param g
 +   * @param alignViewport
 +   * @param seqIndex
 +   * @param starty
 +   * @param yoffset
 +   */
 +  void drawMarker(Graphics2D g, AlignViewport alignViewport, int seqIndex, int starty, int yoffset)
    {
 -
 -    SequenceI[] hseqs = av.getAlignment()
 +    SequenceI[] hseqs = alignViewport.getAlignment()
              .getHiddenSequences().hiddenSequences;
      // Use this method here instead of calling hiddenSeq adjust
      // 3 times.
      int hSize = hseqs.length;
  
 -    int hiddenIndex = i;
 -    int lastIndex = i - 1;
 -    int nextIndex = i + 1;
 +    int hiddenIndex = seqIndex;
 +    int lastIndex = seqIndex - 1;
 +    int nextIndex = seqIndex + 1;
  
      for (int j = 0; j < hSize; j++)
      {
        }
      }
  
 +    /*
 +     * are we below or above the hidden sequences?
 +     */
      boolean below = (hiddenIndex > lastIndex + 1);
      boolean above = (nextIndex > hiddenIndex + 1);
  
 -    gg.setColor(Color.blue);
 +    g.setColor(Color.blue);
 +    int charHeight = av.getCharHeight();
 +
 +    /*
 +     * vertices of the triangle, below or above hidden seqs
 +     */
 +    int[] xPoints = new int[]
 +    { getWidth() - charHeight,
 +        getWidth() - charHeight, getWidth() };
 +    int yShift = seqIndex - starty;
 +
      if (below)
      {
 -      gg.fillPolygon(
 -              new int[]
 -              { getWidth() - av.getCharHeight(),
 -                  getWidth() - av.getCharHeight(), getWidth() },
 -              new int[]
 -              { (i - starty) * av.getCharHeight() + yoffset,
 -                  (i - starty) * av.getCharHeight() + yoffset
 -                          + av.getCharHeight() / 4,
 -                  (i - starty) * av.getCharHeight() + yoffset },
 -              3);
 +      int[] yPoints = new int[] { yShift * charHeight + yoffset,
 +          yShift * charHeight + yoffset + charHeight / 4,
 +          yShift * charHeight + yoffset };
 +      g.fillPolygon(xPoints, yPoints, 3);
      }
      if (above)
      {
 -      gg.fillPolygon(
 -              new int[]
 -              { getWidth() - av.getCharHeight(),
 -                  getWidth() - av.getCharHeight(), getWidth() },
 -              new int[]
 -              { (i - starty + 1) * av.getCharHeight() + yoffset,
 -                  (i - starty + 1) * av.getCharHeight() + yoffset
 -                          - av.getCharHeight() / 4,
 -                  (i - starty + 1) * av.getCharHeight() + yoffset },
 -              3);
 -
 +      yShift++;
 +      int[] yPoints = new int[] { yShift * charHeight + yoffset,
 +          yShift * charHeight + yoffset - charHeight / 4,
 +          yShift * charHeight + yoffset };
 +      g.fillPolygon(xPoints, yPoints, 3);
      }
    }
  
 -  void setHiddenFont(SequenceI seq)
 +  /**
 +   * Answers the standard sequence id font, or a bold font if the sequence is
 +   * set as reference or a hidden group representative
 +   * 
 +   * @param seq
 +   * @param alignViewport
 +   * @return
 +   */
 +  private Font getHiddenFont(SequenceI seq, AlignViewport alignViewport)
    {
 -    Font bold = new Font(av.getFont().getName(), Font.BOLD,
 -            av.getFont().getSize());
 -
      if (av.isReferenceSeq(seq) || av.isHiddenRepSequence(seq))
      {
 -      gg.setFont(bold);
 -    }
 -    else
 -    {
 -      gg.setFont(getIdfont());
 +      return new Font(av.getFont().getName(), Font.BOLD,
 +              av.getFont().getSize());
      }
 +    return getIdfont();
    }
  
    /**
@@@ -23,6 -23,7 +23,6 @@@ package jalview.gui
  import jalview.datamodel.ColumnSelection;
  import jalview.datamodel.HiddenColumns;
  import jalview.datamodel.SequenceGroup;
 -import jalview.datamodel.SequenceI;
  import jalview.renderer.ScaleRenderer;
  import jalview.renderer.ScaleRenderer.ScaleMark;
  import jalview.util.MessageManager;
@@@ -157,7 -158,12 +157,12 @@@ public class ScalePanel extends JPane
    protected void rightMouseButtonPressed(MouseEvent evt, final int res)
    {
      JPopupMenu pop = new JPopupMenu();
-     if (reveal != null)
+     /*
+      * grab the hidden range in case mouseMoved nulls it
+      */
+     final int[] hiddenRange = reveal;
+     if (hiddenRange != null)
      {
        JMenuItem item = new JMenuItem(
                MessageManager.getString("label.reveal"));
          @Override
          public void actionPerformed(ActionEvent e)
          {
-           av.showColumn(reveal[0]);
+           av.showColumn(hiddenRange[0]);
            reveal = null;
+           ap.updateLayout();
            ap.paintAlignment(true, true);
            av.sendSelection();
          }
            {
              av.showAllHiddenColumns();
              reveal = null;
+             ap.updateLayout();
              ap.paintAlignment(true, true);
              av.sendSelection();
            }
              av.setSelectionGroup(null);
            }
  
+           ap.updateLayout();
            ap.paintAlignment(true, true);
            av.sendSelection();
          }
      }
  
      av.getColumnSelection().addElement(res);
 -    SequenceGroup sg = new SequenceGroup();
 -    // try to be as quick as possible
 -    SequenceI[] iVec = av.getAlignment().getSequencesArray();
 -    for (int i = 0; i < iVec.length; i++)
 -    {
 -      sg.addSequence(iVec[i], false);
 -      iVec[i] = null;
 -    }
 -    iVec = null;
 +    SequenceGroup sg = new SequenceGroup(av.getAlignment().getSequences());
      sg.setStartRes(res);
      sg.setEndRes(res);
  
    {
      mouseDragging = false;
  
 -    int res = (evt.getX() / av.getCharWidth())
 +    int xCords = Math.max(0, evt.getX()); // prevent negative X coordinates
 +
 +    int res = (xCords / av.getCharWidth())
              + av.getRanges().getStartRes();
  
      if (av.hasHiddenColumns())
      }
      stretchingGroup = false;
      ap.paintAlignment(false, false);
 +    av.isSelectionGroupChanged(true);
 +    av.isColSelChanged(true);
      av.sendSelection();
    }
  
    /**
     * Action on dragging the mouse in the scale panel is to expand or shrink the
 -   * selection group range (including any hidden columns that it spans)
 +   * selection group range (including any hidden columns that it spans). Note
 +   * that the selection is only broadcast at the start of the drag (on
 +   * mousePressed) and at the end (on mouseReleased), to avoid overload
 +   * redrawing of other views.
     * 
     * @param evt
     */
@@@ -32,6 -32,7 +32,6 @@@ import jalview.util.Comparison
  import jalview.viewmodel.ViewportListenerI;
  import jalview.viewmodel.ViewportRanges;
  
 -import java.awt.AlphaComposite;
  import java.awt.BasicStroke;
  import java.awt.BorderLayout;
  import java.awt.Color;
@@@ -55,11 -56,6 +55,11 @@@ import javax.swing.JComponent
   */
  public class SeqCanvas extends JComponent implements ViewportListenerI
  {
 +  /*
 +   * pixels gap between sequences and annotations when in wrapped mode
 +   */
 +  static final int SEQS_ANNOTATION_GAP = 3;
 +
    private static final String ZEROS = "0000000000";
  
    final FeatureRenderer fr;
@@@ -87,9 -83,9 +87,9 @@@
  
    private int labelWidthWest; // label left width in pixels if shown
  
 -  private int wrappedSpaceAboveAlignment; // gap between widths
 +  int wrappedSpaceAboveAlignment; // gap between widths
  
 -  private int wrappedRepeatHeightPx; // height in pixels of wrapped width
 +  int wrappedRepeatHeightPx; // height in pixels of wrapped width
  
    private int wrappedVisibleWidths; // number of wrapped widths displayed
  
      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)))
      {
 -      BufferedImage lcimg = buildLocalImage(selectImage);
 -      g.drawImage(lcimg, 0, 0, this);
 +      g.drawImage(img, 0, 0, this);
 +
 +      drawSelectionGroup((Graphics2D) g, ranges.getStartRes(),
 +              ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
 +
        fastPaint = false;
      }
 -    else if ((width > 0) && (height > 0))
 +    else if (width > 0 && height > 0)
      {
 -      // 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
 +      /*
 +       * 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;
 -        }
 +        img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
          gg = (Graphics2D) img.getGraphics();
          gg.setFont(av.getFont());
        }
          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);
  
 +      drawSelectionGroup(gg, ranges.getStartRes(),
 +              ranges.getEndRes(), ranges.getStartSeq(), ranges.getEndSeq());
 +
 +      g.drawImage(img, 0, 0, this);
      }
  
      if (av.cursorMode)
    {
      drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
  
 -    BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
 +    drawSelectionGroup((Graphics2D) g1, startRes, endRes,
              startSeq, endSeq);
 -    if (selectImage != null)
 -    {
 -      ((Graphics2D) g1).setComposite(AlphaComposite
 -              .getInstance(AlphaComposite.SRC_OVER));
 -      g1.drawImage(selectImage, 0, 0, this);
 -    }
    }
  
    /**
    public void drawWrappedPanelForPrinting(Graphics g, int canvasWidth,
            int canvasHeight, int startRes)
    {
 -    SequenceGroup group = av.getSelectionGroup();
 -
      drawWrappedPanel(g, canvasWidth, canvasHeight, startRes);
  
 +    SequenceGroup group = av.getSelectionGroup();
      if (group != null)
      {
 -      BufferedImage selectImage = null;
 -      try
 -      {
 -        selectImage = new BufferedImage(canvasWidth, canvasHeight,
 -                BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
 -      } catch (OutOfMemoryError er)
 -      {
 -        System.gc();
 -        System.err.println("Print image OutOfMemory Error.\n" + er);
 -        new OOMWarning("Creating wrapped alignment image for printing", er);
 -      }
 -      if (selectImage != null)
 -      {
 -        Graphics2D g2 = selectImage.createGraphics();
 -        setupSelectionGroup(g2, selectImage);
 -        drawWrappedSelection(g2, group, canvasWidth, canvasHeight,
 +      drawWrappedSelection((Graphics2D) g, group, canvasWidth, canvasHeight,
                  startRes);
 -
 -        g2.setComposite(
 -                AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
 -        g.drawImage(selectImage, 0, 0, this);
 -        g2.dispose();
 -      }
 -    }
 -  }
 -
 -  /*
 -   * Make a local image by combining the cached image img
 -   * with any selection
 -   */
 -  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());
 -    Graphics2D g2d = lcimg.createGraphics();
 -    g2d.drawImage(img, 0, 0, null);
 -
 -    // overlay selection group on lcimg
 -    if (selectImage != null)
 -    {
 -      g2d.setComposite(
 -              AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
 -      g2d.drawImage(selectImage, 0, 0, this);
      }
 -
 -    g2d.dispose();
 -
 -    return lcimg;
 -  }
 -
 -  /*
 -   * Set up a buffered image of the correct height and size for the sequence canvas
 -   */
 -  private BufferedImage setupImage()
 -  {
 -    BufferedImage lcimg = null;
 -
 -    int charWidth = av.getCharWidth();
 -    int charHeight = av.getCharHeight();
 -    
 -    int width = getWidth();
 -    int height = getHeight();
 -
 -    width -= (width % charWidth);
 -    height -= (height % charHeight);
 -
 -    if ((width < 1) || (height < 1))
 -    {
 -      return null;
 -    }
 -
 -    try
 -    {
 -      lcimg = new BufferedImage(width, height,
 -                BufferedImage.TYPE_INT_ARGB); // ARGB so alpha compositing works
 -    } catch (OutOfMemoryError er)
 -    {
 -      System.gc();
 -      System.err.println(
 -              "Group image OutOfMemory Redraw Error.\n" + er);
 -      new OOMWarning("Creating alignment image for display", er);
 -
 -      return null;
 -    }
 -
 -    return lcimg;
    }
  
    /**
      calculateWrappedGeometry(canvasWidth, canvasHeight);
  
      /*
 -     * draw one width at a time (including any scales or annotation shown),
 +     * draw one width at a time (excluding any scales shown),
       * until we have run out of either alignment or vertical space available
       */
      int ypos = wrappedSpaceAboveAlignment;
              * (av.getScaleAboveWrapped() ? 2 : 1);
  
      /*
 -     * height in pixels of the wrapped widths
 +     * compute height in pixels of the wrapped widths
 +     * - start with space above plus sequences
       */
      wrappedRepeatHeightPx = wrappedSpaceAboveAlignment;
 -    // add sequences
 -    wrappedRepeatHeightPx += av.getRanges().getViewportHeight()
 +    wrappedRepeatHeightPx += av.getAlignment().getHeight()
              * charHeight;
 -    // add annotations panel height if shown
 -    wrappedRepeatHeightPx += getAnnotationHeight();
 +
 +    /*
 +     * add annotations panel height if shown
 +     * also gap between sequences and annotations
 +     */
 +    if (av.isShowAnnotation())
 +    {
 +      wrappedRepeatHeightPx += getAnnotationHeight();
 +      wrappedRepeatHeightPx += SEQS_ANNOTATION_GAP; // 3px
 +    }
  
      /*
       * number of visible widths (the last one may be part height),
     * @param endColumn
     * @param canvasHeight
     */
 -  protected void drawWrappedWidth(Graphics g, int ypos, int startColumn,
 -          int endColumn, int canvasHeight)
 +  protected void drawWrappedWidth(Graphics g, final int ypos,
 +          final int startColumn, final int endColumn,
 +          final int canvasHeight)
    {
      ViewportRanges ranges = av.getRanges();
      int viewportWidth = ranges.getViewportWidth();
  
      if (av.isShowAnnotation())
      {
 -      g.translate(0, cHeight + ypos + 3);
 +      final int yShift = cHeight + ypos + SEQS_ANNOTATION_GAP;
 +      g.translate(0, yShift);
        if (annotations == null)
        {
          annotations = new AnnotationPanel(av);
  
        annotations.renderer.drawComponent(annotations, av, g, -1,
                startColumn, endx + 1);
 -      g.translate(0, -cHeight - ypos - 3);
 +      g.translate(0, -yShift);
      }
      g.setClip(clip);
      g.translate(-xOffset, 0);
      int startx = startRes;
      int endx;
      int ypos = hgap; // vertical offset
-     int maxwidth = av.getAlignment().getWidth();
-     if (av.hasHiddenColumns())
-     {
-       maxwidth = av.getAlignment().getHiddenColumns()
-               .absoluteToVisibleColumn(maxwidth);
-     }
+     int maxwidth = av.getAlignment().getVisibleWidth();
  
      // chop the wrapped alignment extent up into panel-sized blocks and treat
      // each block as if it were a block from an unwrapped alignment
 +    g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
 +            BasicStroke.JOIN_ROUND, 3f, new float[]
 +            { 5f, 3f }, 0f));
 +    g.setColor(Color.RED);
      while ((ypos <= canvasHeight) && (startx < maxwidth))
      {
        // set end value to be start + width, or maxwidth, whichever is smaller
        // update horizontal offset
        startx += cWidth;
      }
 +    g.setStroke(new BasicStroke());
    }
  
    int getAnnotationHeight()
  
    }
  
 +  /**
 +   * Draws the outlines of any groups defined on the alignment (excluding the
 +   * current selection group, if any)
 +   * 
 +   * @param g1
 +   * @param startRes
 +   * @param endRes
 +   * @param startSeq
 +   * @param endSeq
 +   * @param offset
 +   */
    void drawGroupsBoundaries(Graphics g1, int startRes, int endRes,
            int startSeq, int endSeq, int offset)
    {
      Graphics2D g = (Graphics2D) g1;
 -    //
 -    // ///////////////////////////////////
 -    // Now outline any areas if necessary
 -    // ///////////////////////////////////
  
      SequenceGroup group = null;
      int groupIndex = -1;
  
      if (group != null)
      {
 -      g.setStroke(new BasicStroke());
 -      g.setColor(group.getOutlineColour());
 -      
        do
        {
 +        g.setColor(group.getOutlineColour());
 +
          drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
                  endSeq, offset);
  
          groupIndex++;
  
 -        g.setStroke(new BasicStroke());
 -
          if (groupIndex >= av.getAlignment().getGroups().size())
          {
            break;
  
    }
  
 -
 -  /*
 -   * Draw the selection group as a separate image and overlay
 +  /**
 +   * Draws the outline of the current selection group (if any)
 +   * 
 +   * @param g
 +   * @param startRes
 +   * @param endRes
 +   * @param startSeq
 +   * @param endSeq
     */
 -  private BufferedImage drawSelectionGroup(int startRes, int endRes,
 +  private void drawSelectionGroup(Graphics2D g, int startRes, int endRes,
            int startSeq, int endSeq)
    {
      SequenceGroup group = av.getSelectionGroup();
      if (group == null)
      {
 -      // nothing to draw
 -      return null;
 +      return;
      }
  
 -    // set up drawing colour
 -    Graphics2D g = (Graphics2D) selectionImage.getGraphics();
 -
 -    setupSelectionGroup(g, selectionImage);
 -
 +    g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
 +            BasicStroke.JOIN_ROUND, 3f, new float[]
 +            { 5f, 3f }, 0f));
 +    g.setColor(Color.RED);
      if (!av.getWrapAlignment())
      {
        drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
        drawWrappedSelection(g, group, getWidth(), getHeight(),
                av.getRanges().getStartRes());
      }
 -
 -    g.dispose();
 -    return selectionImage;
 +    g.setStroke(new BasicStroke());
    }
  
    /**
    }
  
  
 -  /*
 -   * Set up graphics for selection group
 -   */
 -  private void setupSelectionGroup(Graphics2D g,
 -          BufferedImage selectionImage)
 -  {
 -    // 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[]
 -    { 5f, 3f }, 0f));
 -    g.setColor(Color.RED);
 -  }
 -
 -  /*
 +  /**
     * 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)
 +   * 
 +   * @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)
      }
    }
  
 -  /*
 -   * Draw the selection group as a separate image and overlay
 +  /**
 +   * Draws part of a selection group outline
 +   * 
 +   * @param g
 +   * @param group
 +   * @param startRes
 +   * @param endRes
 +   * @param startSeq
 +   * @param endSeq
 +   * @param verticalOffset
     */
    private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
            int startRes, int endRes, int startSeq, int endSeq,
@@@ -1485,51 -1485,33 +1485,81 @@@ public class AlignmentTes
      assertEquals(".PQR...", seq3.getSequenceAsString());
    }
  
 +  /**
 +   * Test for setHiddenColumns, to check it returns true if the hidden columns
 +   * have changed, else false
 +   */
 +  @Test(groups = { "Functional" })
 +  public void testSetHiddenColumns()
 +  {
 +    AlignmentI al = new Alignment(new SequenceI[] {});
 +    assertFalse(al.getHiddenColumns().hasHiddenColumns());
 +
 +    HiddenColumns hc = new HiddenColumns();
 +    assertFalse(al.setHiddenColumns(hc)); // no change
 +    assertSame(hc, al.getHiddenColumns());
 +
 +    hc.hideColumns(2, 4);
 +    assertTrue(al.getHiddenColumns().hasHiddenColumns());
 +
 +    /*
 +     * set a different object but with the same columns hidden
 +     */
 +    HiddenColumns hc2 = new HiddenColumns();
 +    hc2.hideColumns(2, 4);
 +    assertFalse(al.setHiddenColumns(hc2)); // no change
 +    assertSame(hc2, al.getHiddenColumns());
 +
 +    assertTrue(al.setHiddenColumns(null));
 +    assertNull(al.getHiddenColumns());
 +    assertTrue(al.setHiddenColumns(hc));
 +    assertSame(hc, al.getHiddenColumns());
 +
 +    al.getHiddenColumns().hideColumns(10, 12);
 +    hc2.hideColumns(10, 12);
 +    assertFalse(al.setHiddenColumns(hc2)); // no change
 +
 +    /*
 +     * hide columns 15-16 then 17-18 in hc
 +     * hide columns 15-18 in hc2
 +     * these are not now 'equal' objects even though they
 +     * represent the same set of columns
 +     */
 +    assertSame(hc2, al.getHiddenColumns());
 +    hc.hideColumns(15, 16);
 +    hc.hideColumns(17, 18);
 +    hc2.hideColumns(15, 18);
 +    assertFalse(hc.equals(hc2));
 +    assertTrue(al.setHiddenColumns(hc)); // 'changed'
 +  }
++
+   @Test(groups = { "Functional" })
+   public void testGetWidth()
+   {
+     SequenceI seq1 = new Sequence("seq1", "ABCDEF--");
+     SequenceI seq2 = new Sequence("seq2", "-JKLMNO--");
+     SequenceI seq3 = new Sequence("seq2", "-PQR");
+     AlignmentI a = new Alignment(new SequenceI[] { seq1, seq2, seq3 });
+     assertEquals(9, a.getWidth());
+     // width includes hidden columns
+     a.getHiddenColumns().hideColumns(2, 5);
+     assertEquals(9, a.getWidth());
+   }
+   @Test(groups = { "Functional" })
+   public void testGetVisibleWidth()
+   {
+     SequenceI seq1 = new Sequence("seq1", "ABCDEF--");
+     SequenceI seq2 = new Sequence("seq2", "-JKLMNO--");
+     SequenceI seq3 = new Sequence("seq2", "-PQR");
+     AlignmentI a = new Alignment(new SequenceI[] { seq1, seq2, seq3 });
+     assertEquals(9, a.getVisibleWidth());
+     // width excludes hidden columns
+     a.getHiddenColumns().hideColumns(2, 5);
+     assertEquals(5, a.getVisibleWidth());
+   }
  }