Merge remote-tracking branch 'origin/releases/Release_2_10_2b1_Branch'
authorkiramt <k.mourao@dundee.ac.uk>
Mon, 28 Aug 2017 11:15:35 +0000 (12:15 +0100)
committerkiramt <k.mourao@dundee.ac.uk>
Mon, 28 Aug 2017 11:15:35 +0000 (12:15 +0100)
into bug/JAL-2665-2.10.2b

Conflicts:
src/jalview/gui/SeqCanvas.java

1  2 
src/jalview/datamodel/SequenceGroup.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqPanel.java

@@@ -27,8 -27,6 +27,8 @@@ import jalview.renderer.ResidueShaderI
  import jalview.schemes.ColourSchemeI;
  
  import java.awt.Color;
 +import java.beans.PropertyChangeListener;
 +import java.beans.PropertyChangeSupport;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Map;
   */
  public class SequenceGroup implements AnnotatedCollectionI
  {
 +  // TODO ideally this event notification functionality should be separated into
 +  // a
 +  // subclass of ViewportProperties similarly to ViewportRanges. Done here as
 +  // quick fix for JAL-2665
 +  public static final String SEQ_GROUP_CHANGED = "Sequence group changed";
 +
 +  protected PropertyChangeSupport changeSupport = new PropertyChangeSupport(
 +          this);
 +
 +  public void addPropertyChangeListener(PropertyChangeListener listener)
 +  {
 +    changeSupport.addPropertyChangeListener(listener);
 +  }
 +
 +  public void removePropertyChangeListener(PropertyChangeListener listener)
 +  {
 +    changeSupport.removePropertyChangeListener(listener);
 +  }
 +  // end of event notification functionality initialisation
 +
    String groupName;
  
    String description;
        if (s != null && !sequences.contains(s))
        {
          sequences.add(s);
 +        changeSupport.firePropertyChange(SEQ_GROUP_CHANGED,
 +                sequences.size() - 1, sequences.size());
        }
  
        if (recalc)
      conservation.description = "Conservation for group " + getName()
              + " less than " + consPercGaps + "% gaps";
      // preserve width if already set
-     int aWidth = (conservation.annotations != null) ? (endRes < conservation.annotations.length ? conservation.annotations.length
-             : endRes + 1)
+     int aWidth = (conservation.annotations != null)
+             ? (endRes < conservation.annotations.length
+                     ? conservation.annotations.length
+                     : endRes + 1)
              : endRes + 1;
      conservation.annotations = null;
      conservation.annotations = new Annotation[aWidth]; // should be alignment
      consensus.description = "Percent Identity";
      consensusData = cnsns;
      // preserve width if already set
-     int aWidth = (consensus.annotations != null) ? (endRes < consensus.annotations.length ? consensus.annotations.length
-             : endRes + 1)
+     int aWidth = (consensus.annotations != null)
+             ? (endRes < consensus.annotations.length
+                     ? consensus.annotations.length
+                     : endRes + 1)
              : endRes + 1;
      consensus.annotations = null;
      consensus.annotations = new Annotation[aWidth]; // should be alignment width
      synchronized (sequences)
      {
        sequences.remove(s);
 +      changeSupport.firePropertyChange(SEQ_GROUP_CHANGED,
 +              sequences.size() + 1, sequences.size());
  
        if (recalc)
        {
     */
    public void setStartRes(int i)
    {
 +    int before = startRes;
      startRes = i;
 +    changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before, startRes);
    }
  
    /**
     */
    public void setEndRes(int i)
    {
 +    int before = endRes;
      endRes = i;
 +    changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before, endRes);
    }
  
    /**
      ArrayList<AlignmentAnnotation> aa = new ArrayList<>();
      for (AlignmentAnnotation ann : getAlignmentAnnotation())
      {
-       if ((calcId == null || (ann.getCalcId() != null && ann.getCalcId()
-               .equals(calcId)))
-               && (seq == null || (ann.sequenceRef != null && ann.sequenceRef == seq))
-               && (label == null || (ann.label != null && ann.label
-                       .equals(label))))
+       if ((calcId == null || (ann.getCalcId() != null
+               && ann.getCalcId().equals(calcId)))
+               && (seq == null || (ann.sequenceRef != null
+                       && ann.sequenceRef == seq))
+               && (label == null
+                       || (ann.label != null && ann.label.equals(label))))
        {
          aa.add(ann);
        }
    {
      synchronized (sequences)
      {
 +      int before = sequences.size();
        sequences.clear();
 +      changeSupport.firePropertyChange(SEQ_GROUP_CHANGED, before,
 +              sequences.size());
      }
    }
  
    @Override
    public boolean isNucleotide()
    {
-     if (context != null) {
+     if (context != null)
+     {
        return context.isNucleotide();
      }
      return false;
@@@ -70,8 -70,7 +70,7 @@@ import javax.swing.SwingUtilities
   * @version $Revision: 1.161 $
   */
  public class AlignmentPanel extends GAlignmentPanel implements
-         AdjustmentListener, Printable, AlignmentViewPanel,
-         ViewportListenerI
+         AdjustmentListener, Printable, AlignmentViewPanel, ViewportListenerI
  {
    public AlignViewport av;
  
          // is initialised
          if (av.getWrapAlignment())
          {
-           int widthInRes = getSeqPanel().seqCanvas
-                   .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
+           int widthInRes = getSeqPanel().seqCanvas.getWrappedCanvasWidth(
+                   getSeqPanel().seqCanvas.getWidth());
            vpRanges.setViewportWidth(widthInRes);
          }
          else
                    / av.getCharWidth();
            int heightInSeq = getSeqPanel().seqCanvas.getHeight()
                    / av.getCharHeight();
-           
            vpRanges.setViewportWidth(widthInRes);
            vpRanges.setViewportHeight(heightInSeq);
          }
      // to prevent drawing old image
      FontMetrics fm = getFontMetrics(av.getFont());
  
-     scalePanelHolder.setPreferredSize(new Dimension(10, av.getCharHeight()
-             + fm.getDescent()));
-     idSpaceFillerPanel1.setPreferredSize(new Dimension(10, av
-             .getCharHeight() + fm.getDescent()));
+     scalePanelHolder.setPreferredSize(
+             new Dimension(10, av.getCharHeight() + fm.getDescent()));
+     idSpaceFillerPanel1.setPreferredSize(
+             new Dimension(10, av.getCharHeight() + fm.getDescent()));
  
      getIdPanel().getIdCanvas().gg = null;
      getSeqPanel().seqCanvas.img = null;
    {
      Container c = new Container();
  
-     FontMetrics fm = c.getFontMetrics(new Font(av.font.getName(),
-             Font.ITALIC, av.font.getSize()));
+     FontMetrics fm = c.getFontMetrics(
+             new Font(av.font.getName(), Font.ITALIC, av.font.getSize()));
  
      AlignmentI al = av.getAlignment();
      int i = 0;
        }
      }
  
-     return new Dimension(maxwidth < 0 ? idWidth : Math.min(maxwidth,
-             idWidth), 12);
+     return new Dimension(
+             maxwidth < 0 ? idWidth : Math.min(maxwidth, idWidth), 12);
    }
  
    /**
            /*
             * Scroll down to make end of search results visible
             */
-           setScrollValues(vpRanges.getStartRes(), starts + seqIndex - ends
-                   + 1);
+           setScrollValues(vpRanges.getStartRes(),
+                   starts + seqIndex - ends + 1);
          }
          /*
           * Else results are already visible - no need to scroll
         */
        if (annotationHeight + alignmentHeight > availableHeight)
        {
-         annotationHeight = Math.min(annotationHeight, availableHeight - 2
-                 * rowHeight);
+         annotationHeight = Math.min(annotationHeight,
+                 availableHeight - 2 * rowHeight);
        }
      }
      else
      }
      hscroll.addNotify();
  
-     annotationScroller.setPreferredSize(new Dimension(annotationScroller
-             .getWidth(), annotationHeight));
+     annotationScroller.setPreferredSize(
+             new Dimension(annotationScroller.getWidth(), annotationHeight));
  
      Dimension e = idPanel.getSize();
      alabels.setSize(new Dimension(e.width, annotationHeight));
        else
        {
          int widthInRes = (canvasWidth / av.getCharWidth()) - 1;
-         int heightInSeq = (getSeqPanel().seqCanvas.getHeight() / av
-                 .getCharHeight()) - 1;
+         int heightInSeq = (getSeqPanel().seqCanvas.getHeight()
+                 / av.getCharHeight()) - 1;
  
          vpRanges.setViewportWidth(widthInRes);
          vpRanges.setViewportHeight(heightInSeq);
      repaint();
    }
  
    /**
     * Adjust row/column scrollers to show a visible position in the alignment.
     * 
        if (av.hasHiddenColumns())
        {
          // reset the width to exclude hidden columns
-         width = av.getAlignment().getHiddenColumns().findColumnPosition(width);
+         width = av.getAlignment().getHiddenColumns()
+                 .findColumnPosition(width);
        }
  
        hextent = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
        {
          @Override
          public void run()
-       {
+         {
            // When updating scrolling to use ViewportChange events, this code
            // could not be validated and it is not clear if it is now being
            // called. Log warning here in case it is called and unforeseen
            // problems occur
-           Cache.log
-                   .warn("Unexpected path through code: Wrapped jar file opened with wrap alignment set in preferences");
+           Cache.log.warn(
+                   "Unexpected path through code: Wrapped jar file opened with wrap alignment set in preferences");
  
            // scroll to start of panel
            vpRanges.setStartRes(0);
       * Get the horizontal offset to where we draw the sequences.
       * This is idWidth if using a single Graphics context, else zero.
       */
-     final int alignmentGraphicsOffset = idGraphics != alignmentGraphics ? 0 : idWidth;
+     final int alignmentGraphicsOffset = idGraphics != alignmentGraphics ? 0
+             : idWidth;
  
      FontMetrics fm = getFontMetrics(av.getFont());
      int charHeight = av.getCharHeight();
        endSeq = alignmentHeight;
      }
  
-     int pagesHigh = ((alignmentHeight / totalSeq) + 1)
-             * pageHeight;
+     int pagesHigh = ((alignmentHeight / totalSeq) + 1) * pageHeight;
  
      if (av.isShowAnnotation())
      {
      {
        return Printable.NO_SUCH_PAGE;
      }
-     final int alignmentDrawnHeight = (endSeq - startSeq) * charHeight
-             + 3;
+     final int alignmentDrawnHeight = (endSeq - startSeq) * charHeight + 3;
  
      /*
       * draw the Scale at horizontal offset, then reset to top left (0, 0)
        if (av.isRightAlignIds())
        {
          fm = idGraphics.getFontMetrics();
-         xPos = idWidth
-                 - fm.stringWidth(displayId)
-                 - 4;
+         xPos = idWidth - fm.stringWidth(displayId) - 4;
        }
  
        idGraphics.drawString(displayId, xPos,
       * single graphics context), then reset to (0, scale height)
       */
      alignmentGraphics.translate(alignmentGraphicsOffset, scaleHeight);
 -    getSeqPanel().seqCanvas.drawPanel(alignmentGraphics, startRes, endRes,
 -            startSeq, endSeq, 0);
 +    getSeqPanel().seqCanvas.drawPanelForPrint(alignmentGraphics, startRes,
 +            endRes, startSeq, endSeq);
      alignmentGraphics.translate(-alignmentGraphicsOffset, 0);
  
      if (av.isShowAnnotation() && (endSeq == alignmentHeight))
         * draw the annotations starting at 
         * (idOffset, alignmentHeight) from (0, scaleHeight)
         */
-       alignmentGraphics.translate(alignmentGraphicsOffset, alignmentDrawnHeight);
+       alignmentGraphics.translate(alignmentGraphicsOffset,
+               alignmentDrawnHeight);
        getAnnotationPanel().renderer.drawComponent(getAnnotationPanel(), av,
                alignmentGraphics, -1, startRes, endRes + 1);
      }
                .findColumnPosition(maxwidth) - 1;
      }
  
-     int resWidth = getSeqPanel().seqCanvas.getWrappedCanvasWidth(pwidth
-             - idWidth);
+     int resWidth = getSeqPanel().seqCanvas
+             .getWrappedCanvasWidth(pwidth - idWidth);
  
      int totalHeight = cHeight * (maxwidth / resWidth + 1);
  
        }
        if (labels != null)
        {
-         pg.translate(-3,
-                 ypos + (av.getAlignment().getHeight() * av.getCharHeight()));
+         pg.translate(-3, ypos
+                 + (av.getAlignment().getHeight() * av.getCharHeight()));
  
          pg.setFont(av.getFont());
          labels.drawComponent(pg, idWidth);
-         pg.translate(
-                 +3,
-                 -ypos
-                         - (av.getAlignment().getHeight() * av
-                                 .getCharHeight()));
+         pg.translate(+3, -ypos
+                 - (av.getAlignment().getHeight() * av.getCharHeight()));
        }
  
        ypos += cHeight;
        return calculateIdWidth(-1).width + 4;
      }
      Integer idwidth = null;
-     if (onscreen
-             || (idwidth = Cache.getIntegerProperty("FIGURE_FIXEDIDWIDTH")) == null)
+     if (onscreen || (idwidth = Cache
+             .getIntegerProperty("FIGURE_FIXEDIDWIDTH")) == null)
      {
        int w = getIdPanel().getWidth();
        return (w > 0 ? w : calculateIdWidth().width + 4);
    {
      int boarderBottomOffset = 5;
      long pSessionId = System.currentTimeMillis();
-     headless = (System.getProperty("java.awt.headless") != null && System
-             .getProperty("java.awt.headless").equals("true"));
+     headless = (System.getProperty("java.awt.headless") != null
+             && System.getProperty("java.awt.headless").equals("true"));
      if (alignFrame != null && !headless)
      {
        if (file != null)
        {
-         alignFrame.setProgressBar(MessageManager.formatMessage(
-                 "status.saving_file", new Object[] { type.getLabel() }),
-                 pSessionId);
+         alignFrame.setProgressBar(MessageManager
+                 .formatMessage("status.saving_file", new Object[]
+                 { type.getLabel() }), pSessionId);
        }
      }
      try
          }
  
          im = new jalview.util.ImageMaker(this, type, imageAction,
-                 aDimension.getWidth(), aDimension.getHeight()
-                         + boarderBottomOffset, file, imageTitle,
-                 alignFrame, pSessionId, headless);
+                 aDimension.getWidth(),
+                 aDimension.getHeight() + boarderBottomOffset, file,
+                 imageTitle, alignFrame, pSessionId, headless);
          Graphics graphics = im.getGraphics();
          if (av.getWrapAlignment())
          {
          {
            if (graphics != null)
            {
-             printUnwrapped(aDimension.getWidth(), aDimension.getHeight(),
-                     0, graphics, graphics);
+             printUnwrapped(aDimension.getWidth(), aDimension.getHeight(), 0,
+                     graphics, graphics);
              im.writeImage();
            }
          }
          // this duplicates the calculation in getWrappedHeight but adjusts for
          // offscreen idWith
          width = alignFrame.getWidth() - vscroll.getPreferredSize().width
-                 - alignFrame.getInsets().left
-                 - alignFrame.getInsets().right - getVisibleIdWidth()
-                 + getVisibleIdWidth(false);
+                 - alignFrame.getInsets().left - alignFrame.getInsets().right
+                 - getVisibleIdWidth() + getVisibleIdWidth(false);
        }
        else
        {
      {
        try
        {
-         int s, sSize = av.getAlignment().getHeight(), res, alwidth = av
-                 .getAlignment().getWidth(), g, gSize, f, fSize, sy;
+         int s, sSize = av.getAlignment().getHeight(), res,
+                 alwidth = av.getAlignment().getWidth(), g, gSize, f, fSize,
+                 sy;
          PrintWriter out = new PrintWriter(new FileWriter(imgMapFile));
          out.println(jalview.io.HTMLOutput.getImageMapHTML());
          out.println("<img src=\"" + imageName
              String triplet = null;
              if (av.getAlignment().isNucleotide())
              {
-               triplet = ResidueProperties.nucleotideName.get(seq
-                       .getCharAt(res) + "");
+               triplet = ResidueProperties.nucleotideName
+                       .get(seq.getCharAt(res) + "");
              }
              else
              {
-               triplet = ResidueProperties.aa2Triplet.get(seq.getCharAt(res)
-                       + "");
+               triplet = ResidueProperties.aa2Triplet
+                       .get(seq.getCharAt(res) + "");
              }
  
              if (triplet == null)
                    {
                      text.append("<br>");
                      text.append(features[f].getType());
-                     if (features[f].getDescription() != null
-                             && !features[f].getType().equals(
-                                     features[f].getDescription()))
+                     if (features[f].getDescription() != null && !features[f]
+                             .getType().equals(features[f].getDescription()))
                      {
                        text.append(" ").append(features[f].getDescription());
                      }
  
                      if (features[f].getValue("status") != null)
                      {
-                       text.append(" (").append(features[f].getValue("status"))
+                       text.append(" (")
+                               .append(features[f].getValue("status"))
                                .append(")");
                      }
                    }
  
    @Override
    /**
-    * Property change event fired when a change is made to the viewport ranges 
+    * Property change event fired when a change is made to the viewport ranges
     * object associated with this alignment panel's viewport
     */
    public void propertyChange(PropertyChangeEvent evt)
@@@ -30,7 -30,6 +30,7 @@@ import jalview.renderer.ScaleRenderer.S
  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,12 -54,16 +55,12 @@@ public class SeqCanvas extends JCompone
  {
    final FeatureRenderer fr;
  
 -  final SequenceRenderer sr;
 +  final SequenceRenderer seqRdr;
  
    BufferedImage img;
  
    Graphics2D gg;
  
 -  int imgWidth;
 -
 -  int imgHeight;
 -
    AlignViewport av;
  
    boolean fastPaint = false;
  
    int cursorY = 0;
  
 +  int charHeight = 0;
 +
 +  int charWidth = 0;
 +
 +  boolean fastpainting = false;
 +
 +  AnnotationPanel annotations;
 +
    /**
     * Creates a new SeqCanvas object.
     * 
@@@ -92,7 -87,7 +92,7 @@@
      this.av = ap.av;
      updateViewport();
      fr = new FeatureRenderer(ap);
 -    sr = new SequenceRenderer(av);
 +    seqRdr = new SequenceRenderer(av);
      setLayout(new BorderLayout());
      PaintRefresher.Register(this, av.getSequenceSetId());
      setBackground(Color.white);
  
    public SequenceRenderer getSequenceRenderer()
    {
 -    return sr;
 +    return seqRdr;
    }
  
    public FeatureRenderer getFeatureRenderer()
      return fr;
    }
  
 -  int charHeight = 0, charWidth = 0;
 -
    private void updateViewport()
    {
      charHeight = av.getCharHeight();
          {
            g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
          }
-         g.drawLine((mpos * charWidth) + (charWidth / 2), (ypos + 2)
-                 - (charHeight / 2), (mpos * charWidth) + (charWidth / 2),
-                 ypos - 2);
+         g.drawLine((mpos * charWidth) + (charWidth / 2),
+                 (ypos + 2) - (charHeight / 2),
+                 (mpos * charWidth) + (charWidth / 2), ypos - 2);
        }
      }
    }
        {
          int x = LABEL_WEST - fm.stringWidth(String.valueOf(value))
                  - charWidth / 2;
-         g.drawString(value + "", x, (ypos + (i * charHeight))
-                 - (charHeight / 5));
+         g.drawString(value + "", x,
+                 (ypos + (i * charHeight)) - (charHeight / 5));
        }
      }
    }
  
        if (value != -1)
        {
-         g.drawString(String.valueOf(value), 0, (ypos + (i * charHeight))
-                 - (charHeight / 5));
+         g.drawString(String.valueOf(value), 0,
+                 (ypos + (i * charHeight)) - (charHeight / 5));
        }
      }
    }
  
 -  boolean fastpainting = false;
  
    /**
     * need to make this thread safe move alignment rendering in response to
      int transX = 0;
      int transY = 0;
  
 -    gg.copyArea(horizontal * charWidth, vertical * charHeight, imgWidth,
 -            imgHeight, -horizontal * charWidth, -vertical * charHeight);
 +    gg.copyArea(horizontal * charWidth, vertical * charHeight,
 +            img.getWidth(), img.getHeight(), -horizontal * charWidth,
 +            -vertical * charHeight);
  
      if (horizontal > 0) // scrollbar pulled right, image to the left
      {
        }
        else
        {
 -        transY = imgHeight - ((vertical + 1) * charHeight);
 +        transY = img.getHeight() - ((vertical + 1) * charHeight);
        }
      }
      else if (vertical < 0)
    @Override
    public void paintComponent(Graphics g)
    {
 -    updateViewport();
 -    BufferedImage lcimg = img; // take reference since other threads may null
 -    // img and call later.
      super.paintComponent(g);
  
 -    if (lcimg != null && (fastPaint
 +    updateViewport();
 +
 +    ViewportRanges ranges = av.getRanges();
 +
 +    int width = getWidth();
 +    int height = getHeight();
 +
 +    width -= (width % charWidth);
 +    height -= (height % charHeight);
 +
 +    // selectImage is the selection group outline image
 +    BufferedImage selectImage = drawSelectionGroup(
 +            ranges.getStartRes(), ranges.getEndRes(),
 +            ranges.getStartSeq(), ranges.getEndSeq());
 +
 +    if ((img != null) && (fastPaint
              || (getVisibleRect().width != g.getClipBounds().width)
              || (getVisibleRect().height != g.getClipBounds().height)))
      {
 +      BufferedImage lcimg = buildLocalImage(selectImage);
        g.drawImage(lcimg, 0, 0, this);
        fastPaint = false;
 -      return;
 -    }
 -
 -    // this draws the whole of the alignment
 -    imgWidth = getWidth();
 -    imgHeight = getHeight();
 -
 -    imgWidth -= (imgWidth % charWidth);
 -    imgHeight -= (imgHeight % charHeight);
 -
 -    if ((imgWidth < 1) || (imgHeight < 1))
 -    {
 -      return;
      }
 -
 -    if (lcimg == null || imgWidth != lcimg.getWidth()
 -            || imgHeight != lcimg.getHeight())
 +    else if ((width > 0) && (height > 0))
      {
 -      try
 +      // 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())
        {
 -        lcimg = img = new BufferedImage(imgWidth, imgHeight,
 -                BufferedImage.TYPE_INT_RGB);
 +        img = setupImage();
 +        if (img == null)
 +        {
 +          return;
 +        }
          gg = (Graphics2D) img.getGraphics();
          gg.setFont(av.getFont());
 -      } catch (OutOfMemoryError er)
 +      }
 +
 +      if (av.antiAlias)
        {
 -        System.gc();
 -        System.err.println("SeqCanvas OutOfMemory Redraw Error.\n" + er);
 -        new OOMWarning("Creating alignment image for display", er);
 +        gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
 +                RenderingHints.VALUE_ANTIALIAS_ON);
 +      }
 +
 +      gg.setColor(Color.white);
 +      gg.fillRect(0, 0, img.getWidth(), img.getHeight());
  
 -        return;
 +      if (av.getWrapAlignment())
 +      {
 +        drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
        }
 +      else
 +      {
 +        drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
 +                ranges.getStartSeq(), ranges.getEndSeq(), 0);
 +      }
 +
 +      // lcimg is a local *copy* of img which we'll draw selectImage on top of
 +      BufferedImage lcimg = buildLocalImage(selectImage);
 +      g.drawImage(lcimg, 0, 0, this);
      }
 +  }
  
 -    if (av.antiAlias)
 +  /**
 +   * Draw an alignment panel for printing
 +   * 
 +   * @param g1
 +   *          Graphics object to draw with
 +   * @param startRes
 +   *          start residue of print area
 +   * @param endRes
 +   *          end residue of print area
 +   * @param startSeq
 +   *          start sequence of print area
 +   * @param endSeq
 +   *          end sequence of print area
 +   */
 +  public void drawPanelForPrint(Graphics g1, int startRes, int endRes,
 +          int startSeq, int endSeq)
 +  {
 +    BufferedImage selectImage = drawSelectionGroup(startRes, endRes,
 +            startSeq, endSeq);
 +    drawPanel(g1, startRes, endRes, startSeq, endSeq, 0);
 +    ((Graphics2D) g1).setComposite(
 +            AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
 +    g1.drawImage(selectImage, 0, 0, this);
 +  }
 +
 +  /*
 +   * 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());
 +    Graphics2D g2d = lcimg.createGraphics();
 +    g2d.drawImage(img, 0, 0, null);
 +
 +    // overlay selection group on lcimg
 +    if (selectImage != null)
      {
 -      gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
 -              RenderingHints.VALUE_ANTIALIAS_ON);
 +      g2d.setComposite(
 +              AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
 +      g2d.drawImage(selectImage, 0, 0, this);
      }
 +    g2d.dispose();
 +
 +    return lcimg;
 +  }
  
 -    gg.setColor(Color.white);
 -    gg.fillRect(0, 0, imgWidth, imgHeight);
 +  /*
 +   * Set up a buffered image of the correct height and size for the sequence canvas
 +   */
 +  private BufferedImage setupImage()
 +  {
 +    BufferedImage lcimg = null;
  
 -    ViewportRanges ranges = av.getRanges();
 -    if (av.getWrapAlignment())
 +    int width = getWidth();
 +    int height = getHeight();
 +
 +    width -= (width % charWidth);
 +    height -= (height % charHeight);
 +
 +    if ((width < 1) || (height < 1))
      {
 -      drawWrappedPanel(gg, getWidth(), getHeight(), ranges.getStartRes());
 +      return null;
      }
 -    else
 +
 +    try
      {
 -      drawPanel(gg, ranges.getStartRes(), ranges.getEndRes(),
 -              ranges.getStartSeq(), ranges.getEndSeq(), 0);
 -    }
 +      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);
  
 -    g.drawImage(lcimg, 0, 0, this);
 +      return null;
 +    }
  
 +    return lcimg;
    }
  
    /**
            }
  
            gg.fillPolygon(
-                   new int[] { res * charWidth - charHeight / 4,
+                   new int[]
+                   { res * charWidth - charHeight / 4,
                        res * charWidth + charHeight / 4, res * charWidth },
-                   new int[] { ypos - (charHeight / 2),
-                       ypos - (charHeight / 2), ypos - (charHeight / 2) + 8 },
+                   new int[]
+                   { ypos - (charHeight / 2), ypos - (charHeight / 2),
+                       ypos - (charHeight / 2) + 8 },
                    3);
  
          }
            annotations = new AnnotationPanel(av);
          }
  
-         annotations.renderer.drawComponent(annotations, av, g, -1,
-                 startRes, endx + 1);
+         annotations.renderer.drawComponent(annotations, av, g, -1, startRes,
+                 endx + 1);
          g.translate(0, -cHeight - ypos - 3);
        }
        g.setClip(clip);
      }
    }
  
 -  AnnotationPanel annotations;
 +  /*
 +   * Draw a selection group over a wrapped alignment
 +   */
 +  private void drawWrappedSelection(Graphics2D g, SequenceGroup group,
 +          int canvasWidth,
 +          int canvasHeight, int startRes)
 +  {
 +    // height gap above each panel
 +    int hgap = charHeight;
 +    if (av.getScaleAboveWrapped())
 +    {
 +      hgap += charHeight;
 +    }
 +
 +    int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / charWidth;
 +    int cHeight = av.getAlignment().getHeight() * charHeight;
 +
 +    int startx = startRes;
 +    int endx;
 +    int ypos = hgap; // vertical offset
 +    int maxwidth = av.getAlignment().getWidth();
 +
 +    if (av.hasHiddenColumns())
 +    {
 +      maxwidth = av.getAlignment().getHiddenColumns()
 +              .findColumnPosition(maxwidth);
 +    }
 +
 +    // chop the wrapped alignment extent up into panel-sized blocks and treat
 +    // each block as if it were a block from an unwrapped alignment
 +    while ((ypos <= canvasHeight) && (startx < maxwidth))
 +    {
 +      // set end value to be start + width, or maxwidth, whichever is smaller
 +      endx = startx + cWidth - 1;
 +
 +      if (endx > maxwidth)
 +      {
 +        endx = maxwidth;
 +      }
 +
 +      g.translate(LABEL_WEST, 0);
 +
 +      drawUnwrappedSelection(g, group, startx, endx, 0,
 +              av.getAlignment().getHeight() - 1,
 +              ypos);
 +
 +      g.translate(-LABEL_WEST, 0);
 +
 +      // update vertical offset
 +      ypos += cHeight + getAnnotationHeight() + hgap;
 +
 +      // update horizontal offset
 +      startx += cWidth;
 +    }
 +  }
  
    int getAnnotationHeight()
    {
      return annotations.adjustPanelHeight();
    }
  
 -  /**
 -   * DOCUMENT ME!
 +  /*
 +   * Draw an alignment panel for printing
     * 
     * @param g1
 -   *          DOCUMENT ME!
 +   *          Graphics object to draw with
     * @param startRes
 -   *          DOCUMENT ME!
 +   *          start residue of print area
     * @param endRes
 -   *          DOCUMENT ME!
 +   *          end residue of print area
     * @param startSeq
 -   *          DOCUMENT ME!
 +   *          start sequence of print area
     * @param endSeq
 -   *          DOCUMENT ME!
 +   *          end sequence of print area
     * @param offset
 -   *          DOCUMENT ME!
 +   *          vertical offset
     */
 -  public void drawPanel(Graphics g1, int startRes, int endRes, int startSeq,
 -          int endSeq, int offset)
 +  private void drawPanel(Graphics g1, int startRes, int endRes,
 +          int startSeq, int endSeq, int offset)
    {
      updateViewport();
      if (!av.hasHiddenColumns())
  
    }
  
 -  // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
 -  // int x1, int x2, int y1, int y2, int startx, int starty,
    private void draw(Graphics g, int startRes, int endRes, int startSeq,
            int endSeq, int offset)
    {
      g.setFont(av.getFont());
 -    sr.prepare(g, av.isRenderGaps());
 +    seqRdr.prepare(g, av.isRenderGaps());
  
      SequenceI nextSeq;
  
          // empty
          continue;
        }
 -      sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
 +      seqRdr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
                startRes, endRes, offset + ((i - startSeq) * charHeight));
  
        if (av.isShowSequenceFeatures())
        {
-         fr.drawSequence(g, nextSeq, startRes, endRes, offset
-                 + ((i - startSeq) * charHeight), false);
+         fr.drawSequence(g, nextSeq, startRes, endRes,
+                 offset + ((i - startSeq) * charHeight), false);
        }
  
        // / Highlight search Results once all sequences have been drawn
          {
            for (int r = 0; r < visibleResults.length; r += 2)
            {
 -            sr.drawHighlightedText(nextSeq, visibleResults[r],
 -                    visibleResults[r + 1],
 -                    (visibleResults[r] - startRes) * charWidth,
 -                    offset + ((i - startSeq) * charHeight));
 +            seqRdr.drawHighlightedText(nextSeq, visibleResults[r],
 +                    visibleResults[r + 1], (visibleResults[r] - startRes)
 +                            * charWidth, offset
 +                            + ((i - startSeq) * charHeight));
            }
          }
        }
        if (av.cursorMode && cursorY == i && cursorX >= startRes
                && cursorX <= endRes)
        {
 -        sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
 +        seqRdr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * charWidth,
                  offset + ((i - startSeq) * charHeight));
        }
      }
      // ///////////////////////////////////
      // Now outline any areas if necessary
      // ///////////////////////////////////
 -    SequenceGroup group = av.getSelectionGroup();
  
 -    int sx = -1;
 -    int sy = -1;
 -    int ex = -1;
 +    SequenceGroup group = null;
      int groupIndex = -1;
 -    int visWidth = (endRes - startRes + 1) * charWidth;
  
 -    if ((group == null) && (av.getAlignment().getGroups().size() > 0))
 +    if (av.getAlignment().getGroups().size() > 0)
      {
        group = av.getAlignment().getGroups().get(0);
        groupIndex = 0;
  
      if (group != null)
      {
 +      g.setStroke(new BasicStroke());
 +      g.setColor(group.getOutlineColour());
 +      
        do
        {
 -        int oldY = -1;
 -        int i = 0;
 -        boolean inGroup = false;
 -        int top = -1;
 -        int bottom = -1;
 +        drawPartialGroupOutline(g, group, startRes, endRes, startSeq,
 +                endSeq, offset);
-         
++
 +        groupIndex++;
  
 -        for (i = startSeq; i <= endSeq; i++)
 +        g.setStroke(new BasicStroke());
 +
 +        if (groupIndex >= av.getAlignment().getGroups().size())
          {
 -          sx = (group.getStartRes() - startRes) * charWidth;
 -          sy = offset + ((i - startSeq) * charHeight);
 -          ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
 -                  - 1;
 +          break;
 +        }
  
 -          if (sx + ex < 0 || sx > visWidth)
 -          {
 -            continue;
 -          }
 +        group = av.getAlignment().getGroups().get(groupIndex);
  
 -          if ((sx <= (endRes - startRes) * charWidth)
 -                  && group.getSequences(null)
 -                          .contains(av.getAlignment().getSequenceAt(i)))
 -          {
 -            if ((bottom == -1) && !group.getSequences(null)
 -                    .contains(av.getAlignment().getSequenceAt(i + 1)))
 -            {
 -              bottom = sy + charHeight;
 -            }
 -
 -            if (!inGroup)
 -            {
 -              if (((top == -1) && (i == 0)) || !group.getSequences(null)
 -                      .contains(av.getAlignment().getSequenceAt(i - 1)))
 -              {
 -                top = sy;
 -              }
 -
 -              oldY = sy;
 -              inGroup = true;
 -
 -              if (group == av.getSelectionGroup())
 -              {
 -                g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
 -                        BasicStroke.JOIN_ROUND, 3f, new float[]
 -                        { 5f, 3f }, 0f));
 -                g.setColor(Color.RED);
 -              }
 -              else
 -              {
 -                g.setStroke(new BasicStroke());
 -                g.setColor(group.getOutlineColour());
 -              }
 -            }
 -          }
 -          else
 +      } while (groupIndex < av.getAlignment().getGroups().size());
 +
 +    }
 +
 +  }
 +
 +
 +  /*
 +   * Draw the selection group as a separate image and overlay
 +   */
 +  private BufferedImage drawSelectionGroup(int startRes, int endRes,
 +          int startSeq, int endSeq)
 +  {
 +    // get a new image of the correct size
 +    BufferedImage selectionImage = setupImage();
 +
 +    if (selectionImage == null)
 +    {
 +      return null;
 +    }
 +
 +    SequenceGroup group = av.getSelectionGroup();
 +    if (group == null)
 +    {
 +      // nothing to draw
 +      return null;
 +    }
 +
 +    // set up drawing colour
 +    Graphics2D g = (Graphics2D) selectionImage.getGraphics();
 +
 +    // 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);
 +
 +    if (!av.getWrapAlignment())
 +    {
 +      drawUnwrappedSelection(g, group, startRes, endRes, startSeq, endSeq,
 +              0);
 +    }
 +    else
 +    {
 +      drawWrappedSelection(g, group, getWidth(), getHeight(),
 +              av.getRanges().getStartRes());
 +    }
 +
 +    g.dispose();
 +    return selectionImage;
 +  }
 +
 +  /*
 +   * Draw a selection group over an unwrapped alignment
 +   * @param g graphics object to draw with
 +   * @param group selection group
 +   * @param startRes start residue of area to draw
 +   * @param endRes end residue of area to draw
 +   * @param startSeq start sequence of area to draw
 +   * @param endSeq end sequence of area to draw
 +   * @param offset vertical offset (used when called from wrapped alignment code)
 +   */
 +  private void drawUnwrappedSelection(Graphics2D g, SequenceGroup group,
 +          int startRes, int endRes, int startSeq, int endSeq, int offset)
 +  {
 +    if (!av.hasHiddenColumns())
 +    {
 +      drawPartialGroupOutline(g, group, startRes, endRes, startSeq, endSeq,
 +              offset);
 +    }
 +    else
 +    {
 +      // package into blocks of visible columns
 +      int screenY = 0;
 +      int blockStart = startRes;
 +      int blockEnd = endRes;
 +
 +      for (int[] region : av.getAlignment().getHiddenColumns()
 +              .getHiddenColumnsCopy())
 +      {
 +        int hideStart = region[0];
 +        int hideEnd = region[1];
 +
 +        if (hideStart <= blockStart)
 +        {
 +          blockStart += (hideEnd - hideStart) + 1;
 +          continue;
 +        }
 +
 +        blockEnd = hideStart - 1;
 +
 +        g.translate(screenY * charWidth, 0);
 +        drawPartialGroupOutline(g, group,
 +                blockStart, blockEnd, startSeq, endSeq, offset);
 +
 +        g.translate(-screenY * charWidth, 0);
 +        screenY += blockEnd - blockStart + 1;
 +        blockStart = hideEnd + 1;
 +
 +        if (screenY > (endRes - startRes))
 +        {
 +          // already rendered last block
 +          break;
 +        }
 +      }
 +
 +      if (screenY <= (endRes - startRes))
 +      {
 +        // remaining visible region to render
 +        blockEnd = blockStart + (endRes - startRes) - screenY;
 +        g.translate(screenY * charWidth, 0);
 +        drawPartialGroupOutline(g, group,
 +                blockStart, blockEnd, startSeq, endSeq, offset);
 +        
 +        g.translate(-screenY * charWidth, 0);
 +      }
 +    }
 +  }
 +
 +  /*
 +   * Draw the selection group as a separate image and overlay
 +   */
 +  private void drawPartialGroupOutline(Graphics2D g, SequenceGroup group,
 +          int startRes, int endRes, int startSeq, int endSeq,
 +          int verticalOffset)
 +  {
 +    int visWidth = (endRes - startRes + 1) * charWidth;
 +
 +    int oldY = -1;
 +    int i = 0;
 +    boolean inGroup = false;
 +    int top = -1;
 +    int bottom = -1;
 +
 +    int sx = -1;
 +    int sy = -1;
 +    int xwidth = -1;
 +
 +    for (i = startSeq; i <= endSeq; i++)
 +    {
 +      // position of start residue of group relative to startRes, in pixels
 +      sx = (group.getStartRes() - startRes) * charWidth;
 +
 +      // width of group in pixels
 +      xwidth = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
 +              - 1;
 +
 +      sy = verticalOffset + (i - startSeq) * charHeight;
 +
 +      if (sx + xwidth < 0 || sx > visWidth)
 +      {
 +        continue;
 +      }
 +
 +      if ((sx <= (endRes - startRes) * charWidth)
 +              && group.getSequences(null)
 +                      .contains(av.getAlignment().getSequenceAt(i)))
 +      {
 +        if ((bottom == -1) && !group.getSequences(null)
 +                .contains(av.getAlignment().getSequenceAt(i + 1)))
 +        {
 +          bottom = sy + charHeight;
 +        }
 +
 +        if (!inGroup)
 +        {
 +          if (((top == -1) && (i == 0)) || !group.getSequences(null)
 +                  .contains(av.getAlignment().getSequenceAt(i - 1)))
            {
 -            if (inGroup)
 -            {
 -              if (sx >= 0 && sx < visWidth)
 -              {
 -                g.drawLine(sx, oldY, sx, sy);
 -              }
 -
 -              if (sx + ex < visWidth)
 -              {
 -                g.drawLine(sx + ex, oldY, sx + ex, sy);
 -              }
 -
 -              if (sx < 0)
 -              {
 -                ex += sx;
 -                sx = 0;
 -              }
 -
 -              if (sx + ex > visWidth)
 -              {
 -                ex = visWidth;
 -              }
 -
 -              else if (sx + ex >= (endRes - startRes + 1) * charWidth)
 -              {
 -                ex = (endRes - startRes + 1) * charWidth;
 -              }
 -
 -              if (top != -1)
 -              {
 -                g.drawLine(sx, top, sx + ex, top);
 -                top = -1;
 -              }
 -
 -              if (bottom != -1)
 -              {
 -                g.drawLine(sx, bottom, sx + ex, bottom);
 -                bottom = -1;
 -              }
 -
 -              inGroup = false;
 -            }
 +            top = sy;
            }
 -        }
  
 +          oldY = sy;
 +          inGroup = true;
 +        }
 +      }
 +      else
 +      {
          if (inGroup)
          {
 -          sy = offset + ((i - startSeq) * charHeight);
 +          // if start position is visible, draw vertical line to left of
 +          // group
            if (sx >= 0 && sx < visWidth)
            {
              g.drawLine(sx, oldY, sx, sy);
            }
  
 -          if (sx + ex < visWidth)
 +          // if end position is visible, draw vertical line to right of
 +          // group
 +          if (sx + xwidth < visWidth)
            {
 -            g.drawLine(sx + ex, oldY, sx + ex, sy);
 +            g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
            }
  
            if (sx < 0)
            {
 -            ex += sx;
 +            xwidth += sx;
              sx = 0;
            }
  
 -          if (sx + ex > visWidth)
 +          // don't let width extend beyond current block, or group extent
 +          // fixes JAL-2672
 +          if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
            {
 -            ex = visWidth;
 +            xwidth = (endRes - startRes + 1) * charWidth - sx;
            }
 -          else if (sx + ex >= (endRes - startRes + 1) * charWidth)
 -          {
 -            ex = (endRes - startRes + 1) * charWidth;
 -          }
 -
 +          
 +          // draw horizontal line at top of group
            if (top != -1)
            {
 -            g.drawLine(sx, top, sx + ex, top);
 +            g.drawLine(sx, top, sx + xwidth, top);
              top = -1;
            }
  
 +          // draw horizontal line at bottom of group
            if (bottom != -1)
            {
 -            g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
 +            g.drawLine(sx, bottom, sx + xwidth, bottom);
              bottom = -1;
            }
  
            inGroup = false;
          }
 +      }
 +    }
  
 -        groupIndex++;
 +    if (inGroup)
 +    {
 +      sy = verticalOffset + ((i - startSeq) * charHeight);
 +      if (sx >= 0 && sx < visWidth)
 +      {
 +        g.drawLine(sx, oldY, sx, sy);
 +      }
  
 -        g.setStroke(new BasicStroke());
 +      if (sx + xwidth < visWidth)
 +      {
 +        g.drawLine(sx + xwidth, oldY, sx + xwidth, sy);
 +      }
  
 -        if (groupIndex >= av.getAlignment().getGroups().size())
 -        {
 -          break;
 -        }
 +      if (sx < 0)
 +      {
 +        xwidth += sx;
 +        sx = 0;
 +      }
  
 -        group = av.getAlignment().getGroups().get(groupIndex);
 +      if (sx + xwidth > visWidth)
 +      {
 +        xwidth = visWidth;
 +      }
 +      else if (sx + xwidth >= (endRes - startRes + 1) * charWidth)
 +      {
 +        xwidth = (endRes - startRes + 1) * charWidth;
 +      }
  
 -      } while (groupIndex < av.getAlignment().getGroups().size());
 +      if (top != -1)
 +      {
 +        g.drawLine(sx, top, sx + xwidth, top);
 +        top = -1;
 +      }
  
 -    }
 +      if (bottom != -1)
 +      {
 +        g.drawLine(sx, bottom - 1, sx + xwidth, bottom - 1);
 +        bottom = -1;
 +      }
  
 +      inGroup = false;
 +    }
    }
 -
 +  
    /**
     * DOCUMENT ME!
     * 
    {
      String eventName = evt.getPropertyName();
  
 -    if (av.getWrapAlignment())
 +    if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
 +    {
 +      fastPaint = true;
 +      repaint();
 +    }
 +    else if (av.getWrapAlignment())
      {
        if (eventName.equals(ViewportRanges.STARTRES))
        {
@@@ -74,9 -74,9 +74,9 @@@ import javax.swing.ToolTipManager
   * @author $author$
   * @version $Revision: 1.130 $
   */
- public class SeqPanel extends JPanel implements MouseListener,
-         MouseMotionListener, MouseWheelListener, SequenceListener,
-         SelectionListener
+ public class SeqPanel extends JPanel
+         implements MouseListener, MouseMotionListener, MouseWheelListener,
+         SequenceListener, SelectionListener
  
  {
    /** DOCUMENT ME!! */
  
        y -= hgap;
  
-       seq = Math.min((y % cHeight) / av.getCharHeight(), av.getAlignment()
-               .getHeight() - 1);
+       seq = Math.min((y % cHeight) / av.getCharHeight(),
+               av.getAlignment().getHeight() - 1);
      }
      else
      {
-       seq = Math.min((y / av.getCharHeight())
-               + av.getRanges().getStartSeq(),
-               av
-               .getAlignment().getHeight() - 1);
+       seq = Math.min(
+               (y / av.getCharHeight()) + av.getRanges().getStartSeq(),
+               av.getAlignment().getHeight() - 1);
      }
  
      return seq;
        if (editCommand != null && editCommand.getSize() > 0)
        {
          ap.alignFrame.addHistoryItem(editCommand);
-         av.firePropertyChange("alignment", null, av.getAlignment()
-                 .getSequences());
+         av.firePropertyChange("alignment", null,
+                 av.getAlignment().getSequences());
        }
      } finally
      {
  
      HiddenColumns hidden = av.getAlignment().getHiddenColumns();
  
-     if (av.hasHiddenColumns()
-  && !hidden.isVisible(seqCanvas.cursorX))
+     if (av.hasHiddenColumns() && !hidden.isVisible(seqCanvas.cursorX))
      {
        int original = seqCanvas.cursorX - dx;
        int maxWidth = av.getAlignment().getWidth();
      Point p = lastp;
      if (!event.isShiftDown() || p == null)
      {
-       p = (tooltipText != null && tooltipText.length() > 6) ? new Point(
-               event.getX() + wdth, event.getY() - 20) : null;
+       p = (tooltipText != null && tooltipText.length() > 6)
+               ? new Point(event.getX() + wdth, event.getY() - 20)
+               : null;
      }
      /*
       * TODO: try to modify position region is not obcured by tooltip
        }
        else
        {
-         residue = "X".equalsIgnoreCase(displayChar) ? "X" : ("*"
-                 .equals(displayChar) ? "STOP"
-                 : ResidueProperties.aa2Triplet.get(displayChar));
+         residue = "X".equalsIgnoreCase(displayChar) ? "X"
+                 : ("*".equals(displayChar) ? "STOP"
+                         : ResidueProperties.aa2Triplet.get(displayChar));
        }
        text.append(" ").append(nucleotide ? "Nucleotide" : "Residue")
                .append(": ").append(residue == null ? displayChar : residue);
        int oldWidth = av.getCharWidth();
  
        // Which is bigger, left-right or up-down?
-       if (Math.abs(evt.getY() - lastMousePress.getY()) > Math.abs(evt
-               .getX() - lastMousePress.getX()))
+       if (Math.abs(evt.getY() - lastMousePress.getY()) > Math
+               .abs(evt.getX() - lastMousePress.getX()))
        {
          /*
           * on drag up or down, decrement or increment font size
        }
        if (editCommand == null)
        {
-         editCommand = new EditCommand(MessageManager.formatMessage(
-                 "label.edit_params", new String[] { label }));
+         editCommand = new EditCommand(MessageManager
+                 .formatMessage("label.edit_params", new String[]
+                 { label }));
        }
      }
  
      ap.alignFrame.statusBar.setText(message.toString());
  
      // Are we editing within a selection group?
-     if (groupEditing
-             || (sg != null && sg.getSequences(av.getHiddenRepSequences())
-                     .contains(seq)))
+     if (groupEditing || (sg != null
+             && sg.getSequences(av.getHiddenRepSequences()).contains(seq)))
      {
        fixedColumns = true;
  
          }
          else
          {
-           appendEdit(Action.INSERT_GAP, groupSeqs, startres, startres
-                   - lastres);
+           appendEdit(Action.INSERT_GAP, groupSeqs, startres,
+                   startres - lastres);
          }
        }
        else
          }
          else
          {
-           appendEdit(Action.DELETE_GAP, groupSeqs, startres, lastres
-                   - startres);
+           appendEdit(Action.DELETE_GAP, groupSeqs, startres,
+                   lastres - startres);
          }
  
        }
           * highlight the first feature at the position on the alignment
           */
          SearchResultsI highlight = new SearchResults();
-         highlight.addResult(sequence, features.get(0).getBegin(), features
-                 .get(0).getEnd());
+         highlight.addResult(sequence, features.get(0).getBegin(),
+                 features.get(0).getEnd());
          seqCanvas.highlightSearchResults(highlight);
  
          /*
  
      if (av.getWrapAlignment() && seq > av.getAlignment().getHeight())
      {
-       JvOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
-               .getString("label.cannot_edit_annotations_in_wrapped_view"),
+       JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+               MessageManager.getString(
+                       "label.cannot_edit_annotations_in_wrapped_view"),
                MessageManager.getString("label.wrapped_view_no_edit"),
                JvOptionPane.WARNING_MESSAGE);
        return;
  
      if (stretchGroup == null)
      {
 -      // Only if left mouse button do we want to change group sizes
 +      createStretchGroup(res, sequence);
 +    }
  
 -      // define a new group here
 -      SequenceGroup sg = new SequenceGroup();
 -      sg.setStartRes(res);
 -      sg.setEndRes(res);
 -      sg.addSequence(sequence, false);
 -      av.setSelectionGroup(sg);
 -      stretchGroup = sg;
 +    if (stretchGroup != null)
 +    {
 +      stretchGroup.addPropertyChangeListener(seqCanvas);
 +    }
  
 -      if (av.getConservationSelected())
 -      {
 -        SliderPanel.setConservationSlider(ap, av.getResidueShading(),
 -                ap.getViewName());
 -      }
 +    seqCanvas.repaint();
 +  }
  
 -      if (av.getAbovePIDThreshold())
 -      {
 -        SliderPanel.setPIDSliderSource(ap, av.getResidueShading(),
 -                ap.getViewName());
 -      }
 -      // TODO: stretchGroup will always be not null. Is this a merge error ?
 -      if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
 -      {
 -        // Edit end res position of selected group
 -        changeEndRes = true;
 -      }
 -      else if ((stretchGroup != null)
 -              && (stretchGroup.getStartRes() == res))
 -      {
 -        // Edit end res position of selected group
 -        changeStartRes = true;
 -      }
 -      stretchGroup.getWidth();
 +  private void createStretchGroup(int res, SequenceI sequence)
 +  {
 +    // Only if left mouse button do we want to change group sizes
 +    // define a new group here
 +    SequenceGroup sg = new SequenceGroup();
 +    sg.setStartRes(res);
 +    sg.setEndRes(res);
 +    sg.addSequence(sequence, false);
 +    av.setSelectionGroup(sg);
 +    stretchGroup = sg;
 +
 +    if (av.getConservationSelected())
 +    {
 +      SliderPanel.setConservationSlider(ap, av.getResidueShading(),
 +              ap.getViewName());
      }
  
 -    seqCanvas.repaint();
 +    if (av.getAbovePIDThreshold())
 +    {
 +      SliderPanel.setPIDSliderSource(ap, av.getResidueShading(),
 +              ap.getViewName());
 +    }
 +    // TODO: stretchGroup will always be not null. Is this a merge error ?
 +    // or is there a threading issue here?
 +    if ((stretchGroup != null) && (stretchGroup.getEndRes() == res))
 +    {
 +      // Edit end res position of selected group
 +      changeEndRes = true;
 +    }
 +    else if ((stretchGroup != null) && (stretchGroup.getStartRes() == res))
 +    {
 +      // Edit end res position of selected group
 +      changeStartRes = true;
 +    }
 +    stretchGroup.getWidth();
 +
    }
  
    /**
      {
        return;
      }
 +
 +    stretchGroup.removePropertyChangeListener(seqCanvas);
 +
      // always do this - annotation has own state
      // but defer colourscheme update until hidden sequences are passed in
      boolean vischange = stretchGroup.recalcConservation(true);
        stretchGroup.cs.alignmentChanged(stretchGroup,
                av.getHiddenRepSequences());
  
-       ResidueShaderI groupColourScheme = stretchGroup.getGroupColourScheme();
+       ResidueShaderI groupColourScheme = stretchGroup
+               .getGroupColourScheme();
        String name = stretchGroup.getName();
        if (stretchGroup.cs.conservationApplied())
        {
      {
        scrollThread.setEvent(evt);
      }
 -
 -    seqCanvas.repaint();
    }
  
    void scrollCanvas(MouseEvent evt)
              running = av.getRanges().scrollUp(true);
            }
  
-           if (mouseDragging && (evt.getY() >= getHeight())
-                   && (av.getAlignment().getHeight() > av.getRanges()
-                           .getEndSeq()))
+           if (mouseDragging && (evt.getY() >= getHeight()) && (av
+                   .getAlignment().getHeight() > av.getRanges().getEndSeq()))
            {
              running = av.getRanges().scrollUp(false);
            }
      // handles selection messages...
      // TODO: extend config options to allow user to control if selections may be
      // shared between viewports.
-     boolean iSentTheSelection = (av == source || (source instanceof AlignViewport && ((AlignmentViewport) source)
-             .getSequenceSetId().equals(av.getSequenceSetId())));
+     boolean iSentTheSelection = (av == source
+             || (source instanceof AlignViewport
+                     && ((AlignmentViewport) source).getSequenceSetId()
+                             .equals(av.getSequenceSetId())));
  
      if (iSentTheSelection)
      {
        repaint = true;
      }
  
-     if (copycolsel
-             && av.hasHiddenColumns()
+     if (copycolsel && av.hasHiddenColumns()
              && (av.getAlignment().getHiddenColumns() == null))
      {
        System.err.println("Bad things");