Merge branch 'bug/JAL-2934proportionalScrolling' into develop
[jalview.git] / src / jalview / gui / AlignmentPanel.java
index 77a76fd..8e7c745 100644 (file)
  */
 package jalview.gui;
 
-import jalview.analysis.AnnotationSorter;
-import jalview.api.AlignViewportI;
-import jalview.api.AlignmentViewPanel;
-import jalview.bin.Cache;
-import jalview.datamodel.AlignmentI;
-import jalview.datamodel.HiddenColumns;
-import jalview.datamodel.SearchResultsI;
-import jalview.datamodel.SequenceFeature;
-import jalview.datamodel.SequenceGroup;
-import jalview.datamodel.SequenceI;
-import jalview.io.HTMLOutput;
-import jalview.jbgui.GAlignmentPanel;
-import jalview.math.AlignmentDimension;
-import jalview.schemes.ResidueProperties;
-import jalview.structure.StructureSelectionManager;
-import jalview.util.Comparison;
-import jalview.util.MessageManager;
-import jalview.viewmodel.ViewportListenerI;
-import jalview.viewmodel.ViewportRanges;
-
 import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.Container;
@@ -65,15 +45,48 @@ import java.util.List;
 import javax.swing.BoundedRangeModel;
 import javax.swing.SwingUtilities;
 
+import jalview.analysis.AnnotationSorter;
+import jalview.api.AlignViewportI;
+import jalview.api.AlignmentViewPanel;
+import jalview.bin.Cache;
+import jalview.bin.Console;
+import jalview.bin.Jalview;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.SearchResultsI;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.gui.ImageExporter.ImageWriterI;
+import jalview.io.HTMLOutput;
+import jalview.io.exceptions.ImageOutputException;
+import jalview.jbgui.GAlignmentPanel;
+import jalview.math.AlignmentDimension;
+import jalview.schemes.ResidueProperties;
+import jalview.structure.StructureSelectionManager;
+import jalview.util.Comparison;
+import jalview.util.ImageMaker;
+import jalview.util.MessageManager;
+import jalview.util.imagemaker.BitmapImageSizing;
+import jalview.viewmodel.ViewportListenerI;
+import jalview.viewmodel.ViewportRanges;
+
 /**
  * DOCUMENT ME!
  * 
  * @author $author$
  * @version $Revision: 1.161 $
  */
+@SuppressWarnings("serial")
 public class AlignmentPanel extends GAlignmentPanel implements
         AdjustmentListener, Printable, AlignmentViewPanel, ViewportListenerI
 {
+  /*
+   * spare space in pixels between sequence id and alignment panel
+   */
+  private static final int ID_WIDTH_PADDING = 4;
+
   public AlignViewport av;
 
   OverviewPanel overviewPanel;
@@ -82,11 +95,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
   private IdPanel idPanel;
 
-  private boolean headless;
-
   IdwidthAdjuster idwidthAdjuster;
 
-  /** DOCUMENT ME!! */
   public AlignFrame alignFrame;
 
   private ScalePanel scalePanel;
@@ -117,6 +127,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
    */
   public AlignmentPanel(AlignFrame af, final AlignViewport av)
   {
+    // setBackground(Color.white); // BH 2019
     alignFrame = af;
     this.av = av;
     setSeqPanel(new SeqPanel(av, this));
@@ -223,19 +234,21 @@ public class AlignmentPanel extends GAlignmentPanel implements
     // to prevent drawing old image
     FontMetrics fm = getFontMetrics(av.getFont());
 
+    // update the flag controlling whether the grid is too small to render the
+    // font
+    av.validCharWidth = fm.charWidth('M') <= av.getCharWidth();
+
     scalePanelHolder.setPreferredSize(
             new Dimension(10, av.getCharHeight() + fm.getDescent()));
     idSpaceFillerPanel1.setPreferredSize(
             new Dimension(10, av.getCharHeight() + fm.getDescent()));
     idwidthAdjuster.invalidate();
     scalePanelHolder.invalidate();
-    getIdPanel().getIdCanvas().gg = null;
+    // BH 2018 getIdPanel().getIdCanvas().gg = null;
     getSeqPanel().seqCanvas.img = null;
     getAnnotationPanel().adjustPanelHeight();
 
     Dimension d = calculateIdWidth();
-
-    d.setSize(d.width + 4, d.height);
     getIdPanel().getIdCanvas().setPreferredSize(d);
     hscrollFillerPanel.setPreferredSize(d);
 
@@ -243,21 +256,24 @@ public class AlignmentPanel extends GAlignmentPanel implements
   }
 
   /**
-   * Calculate the width of the alignment labels based on the displayed names
+   * Calculates the width of the alignment labels based on the displayed names
    * and any bounds on label width set in preferences.
    * 
+   * The calculated width is set as a property of the viewport and the layout is
+   * updated.
+   * 
    * @return Dimension giving the maximum width of the alignment label panel
    *         that should be used.
    */
   public Dimension calculateIdWidth()
   {
+    int oldWidth = av.getIdWidth();
+
     // calculate sensible default width when no preference is available
     Dimension r = null;
     if (av.getIdWidth() < 0)
     {
-      int afwidth = (alignFrame != null ? alignFrame.getWidth() : 300);
-      int maxwidth = Math.max(20, Math.min(afwidth - 200, 2 * afwidth / 3));
-      r = calculateIdWidth(maxwidth);
+      r = calculateDefaultAlignmentIdWidth();
       av.setIdWidth(r.width);
     }
     else
@@ -266,19 +282,90 @@ public class AlignmentPanel extends GAlignmentPanel implements
       r.width = av.getIdWidth();
       r.height = 0;
     }
+
+    /*
+     * fudge: if desired width has changed, update layout
+     * (see also paintComponent - updates layout on a repaint)
+     */
+    if (r.width != oldWidth)
+    {
+      idPanelHolder.setPreferredSize(r);
+      validate();
+    }
     return r;
   }
 
+  public Dimension calculateDefaultAlignmentIdWidth()
+  {
+    return calculateIdWidth(-1, false, false);
+  }
+
+  /**
+   * pre 2.11.3 Id width calculation - used when importing old projects only
+   * 
+   * @return
+   */
+  public int getLegacyIdWidth()
+  {
+    int afwidth = (alignFrame != null ? alignFrame.getWidth() : 300);
+    int idWidth = Math.min(afwidth - 200, 2 * afwidth / 3);
+    int maxwidth = Math.max(IdwidthAdjuster.MIN_ID_WIDTH, idWidth);
+    Dimension w = calculateIdWidthOrLegacy(true, maxwidth, false, false);
+    return w.width;
+  }
+
+  /**
+   * Calculate the width of the alignment labels based on the displayed names
+   * and any bounds on label width set in preferences. Also includes annotations
+   * not actually visible.
+   * 
+   * FIXME JAL-244 JAL-4091 - doesn't include sequence associated annotation
+   * label decorators and only called during tests
+   * 
+   * @param maxwidth
+   *          -1 or maximum width allowed for IdWidth
+   * @return Dimension giving the maximum width of the alignment label panel
+   *         that should be used.
+   */
+  protected Dimension calculateIdWidth(int maxwidth)
+  {
+    return calculateIdWidth(maxwidth, true, false);
+  }
+
   /**
    * Calculate the width of the alignment labels based on the displayed names
    * and any bounds on label width set in preferences.
    * 
    * @param maxwidth
    *          -1 or maximum width allowed for IdWidth
+   * @param includeAnnotations
+   *          - when true includes width of any additional marks in annotation
+   *          id panel
+   * @param visibleOnly
+   *          - when true, ignore label widths for hidden annotation rows
    * @return Dimension giving the maximum width of the alignment label panel
    *         that should be used.
    */
-  public Dimension calculateIdWidth(int maxwidth)
+  public Dimension calculateIdWidth(int maxwidth,
+          boolean includeAnnotations, boolean visibleOnly)
+  {
+    return calculateIdWidthOrLegacy(false, maxwidth, includeAnnotations,
+            visibleOnly);
+  }
+
+  /**
+   * legacy mode or post 2.11.3 ID width calculation
+   * 
+   * @param legacy
+   *          - uses annotation labels, not rendered label width (excludes
+   *          additional decorators)
+   * @param maxwidth
+   * @param includeAnnotations
+   * @param visibleOnly
+   * @return
+   */
+  private Dimension calculateIdWidthOrLegacy(boolean legacy, int maxwidth,
+          boolean includeAnnotations, boolean visibleOnly)
   {
     Container c = new Container();
 
@@ -288,44 +375,48 @@ public class AlignmentPanel extends GAlignmentPanel implements
     AlignmentI al = av.getAlignment();
     int i = 0;
     int idWidth = 0;
-    String id;
 
     while ((i < al.getHeight()) && (al.getSequenceAt(i) != null))
     {
       SequenceI s = al.getSequenceAt(i);
-
-      id = s.getDisplayId(av.getShowJVSuffix());
-
-      if (fm.stringWidth(id) > idWidth)
-      {
-        idWidth = fm.stringWidth(id);
-      }
-
+      String id = s.getDisplayId(av.getShowJVSuffix());
+      int stringWidth = fm.stringWidth(id);
+      idWidth = Math.max(idWidth, stringWidth);
       i++;
     }
 
     // Also check annotation label widths
-    i = 0;
-
-    if (al.getAlignmentAnnotation() != null)
+    if (includeAnnotations && al.getAlignmentAnnotation() != null)
     {
       fm = c.getFontMetrics(getAlabels().getFont());
 
-      while (i < al.getAlignmentAnnotation().length)
+      if (!legacy || Jalview.isHeadlessMode())
       {
-        String label = al.getAlignmentAnnotation()[i].label;
-
-        if (fm.stringWidth(label) > idWidth)
+        AnnotationLabels aal = getAlabels();
+        int stringWidth = aal.drawLabels(null, false, idWidth, false, false,
+                fm, !visibleOnly);
+        idWidth = Math.max(idWidth, stringWidth);
+      }
+      else
+      {
+        for (i = 0; i < al.getAlignmentAnnotation().length; i++)
         {
-          idWidth = fm.stringWidth(label);
+          AlignmentAnnotation aa = al.getAlignmentAnnotation()[i];
+          if (visibleOnly && !aa.visible)
+          {
+            continue;
+          }
+          String label = aa.label;
+          int stringWidth = fm.stringWidth(label);
+          idWidth = Math.max(idWidth, stringWidth);
         }
-
-        i++;
       }
     }
 
-    return new Dimension(
-            maxwidth < 0 ? idWidth : Math.min(maxwidth, idWidth), 12);
+    int w = maxwidth < 0 ? idWidth : Math.min(maxwidth, idWidth);
+    w += ID_WIDTH_PADDING;
+
+    return new Dimension(w, 12);
   }
 
   /**
@@ -336,9 +427,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
   {
     boolean scrolled = scrollToPosition(results, 0, false);
 
-    boolean noFastPaint = scrolled && av.getWrapAlignment();
+    boolean fastPaint = !(scrolled && av.getWrapAlignment());
 
-    getSeqPanel().seqCanvas.highlightSearchResults(results, noFastPaint);
+    getSeqPanel().seqCanvas.highlightSearchResults(results, fastPaint);
   }
 
   /**
@@ -461,8 +552,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
         /*
          * Scroll down to make end of search results visible
          */
-        setScrollValues(ranges.getStartRes(), starts + seqIndex - ends
-                + 1);
+        setScrollValues(ranges.getStartRes(), starts + seqIndex - ends + 1);
       }
       /*
        * Else results are already visible - no need to scroll
@@ -527,7 +617,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
     // this is called after loading new annotation onto alignment
     if (alignFrame.getHeight() == 0)
     {
-      System.out.println("NEEDS FIXING");
+      jalview.bin.Console.error(
+              "adjustAnnotationHeight called with zero height alignment window");
     }
     validateAnnotationDimensions(true);
     addNotify();
@@ -542,21 +633,38 @@ public class AlignmentPanel extends GAlignmentPanel implements
    */
   protected void validateAnnotationDimensions(boolean adjustPanelHeight)
   {
-    int annotationHeight = getAnnotationPanel().adjustPanelHeight();
-    annotationHeight = getAnnotationPanel()
-            .adjustForAlignFrame(adjustPanelHeight, annotationHeight);
+    // BH 2018.04.18 comment: addNotify() is not appropriate here. We
+    // are not changing ancestors, and keyboard action listeners do
+    // not need to be reset. addNotify() is a very expensive operation,
+    // requiring a full re-layout of all parents and children.
+    // Note in JComponent:
+    // This method is called by the toolkit internally and should
+    // not be called directly by programs.
+    // I note that addNotify() is called in several areas of Jalview.
 
-    hscroll.addNotify();
-    annotationScroller.setPreferredSize(
-            new Dimension(annotationScroller.getWidth(), annotationHeight));
+    AnnotationPanel ap = getAnnotationPanel();
+    int annotationHeight = ap.adjustPanelHeight();
+    annotationHeight = ap.adjustForAlignFrame(adjustPanelHeight,
+            annotationHeight);
 
+    hscroll.addNotify();
     Dimension e = idPanel.getSize();
-    alabels.setSize(new Dimension(e.width, annotationHeight));
+    int idWidth = e.width;
+    boolean manuallyAdjusted = this.getIdPanel().getIdCanvas()
+            .isManuallyAdjusted();
+    annotationScroller.setPreferredSize(new Dimension(
+            manuallyAdjusted ? idWidth : annotationScroller.getWidth(),
+            annotationHeight));
+
+    alabels.setPreferredSize(new Dimension(idWidth, annotationHeight));
 
     annotationSpaceFillerHolder.setPreferredSize(new Dimension(
-            annotationSpaceFillerHolder.getWidth(), annotationHeight));
+            manuallyAdjusted ? idWidth
+                    : annotationSpaceFillerHolder.getWidth(),
+            annotationHeight));
     annotationScroller.validate();
     annotationScroller.addNotify();
+    ap.validate();
   }
 
   /**
@@ -572,9 +680,10 @@ public class AlignmentPanel extends GAlignmentPanel implements
     boolean wrap = av.getWrapAlignment();
     ViewportRanges ranges = av.getRanges();
     ranges.setStartSeq(0);
-    scalePanelHolder.setVisible(!wrap);
+    // scalePanelHolder.setVisible(!wrap);
     hscroll.setVisible(!wrap);
-    idwidthAdjuster.setVisible(!wrap);
+    // Allow idPanel width adjustment in wrap mode
+    idwidthAdjuster.setVisible(true);
 
     if (wrap)
     {
@@ -608,7 +717,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
       }
     }
 
-    idSpaceFillerPanel1.setVisible(!wrap);
+    // idSpaceFillerPanel1.setVisible(!wrap);
 
     repaint();
   }
@@ -802,7 +911,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
           // 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(
+          Console.warn(
                   "Unexpected path through code: Wrapped jar file opened with wrap alignment set in preferences");
 
           // scroll to start of panel
@@ -841,20 +950,31 @@ public class AlignmentPanel extends GAlignmentPanel implements
     }
   }
 
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param g
-   *          DOCUMENT ME!
-   */
   @Override
   public void paintComponent(Graphics g)
   {
     invalidate(); // needed so that the id width adjuster works correctly
-
     Dimension d = getIdPanel().getIdCanvas().getPreferredSize();
-    idPanelHolder.setPreferredSize(d);
-    hscrollFillerPanel.setPreferredSize(new Dimension(d.width, 12));
+    int idWidth = d.width;
+
+    // check wrapped alignment as at least 1 residue width
+    if (av.getWrapAlignment())
+    {
+      SeqCanvas sc = this.getSeqPanel().seqCanvas;
+      if (sc != null && sc.getWidth() < sc.getMinimumWrappedCanvasWidth())
+      {
+        // need to make some adjustments
+        idWidth -= (sc.getMinimumWrappedCanvasWidth() - sc.getWidth());
+        av.setIdWidth(idWidth);
+        av.getAlignPanel().getIdPanel().getIdCanvas()
+                .setManuallyAdjusted(true);
+
+        validateAnnotationDimensions(false);
+      }
+    }
+
+    idPanelHolder.setPreferredSize(new Dimension(idWidth, d.height));
+    hscrollFillerPanel.setPreferredSize(new Dimension(idWidth, 12));
 
     validate(); // needed so that the id width adjuster works correctly
 
@@ -864,6 +984,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
      */
     ViewportRanges ranges = av.getRanges();
     setScrollValues(ranges.getStartRes(), ranges.getStartSeq());
+    super.paintComponent(g);
   }
 
   /**
@@ -944,8 +1065,16 @@ public class AlignmentPanel extends GAlignmentPanel implements
           Graphics idGraphics, Graphics alignmentGraphics)
           throws PrinterException
   {
-    final int idWidth = getVisibleIdWidth(false);
-
+    final int idWidth, idWidthForGui;
+    // otherwise calculate it
+    idWidth = getVisibleIdWidth(false);
+    // if (getIdPanel()!=null && getIdPanel().getWidth()>0)
+    // {
+    // // use the current IdPanel's width, if its set and non-zero
+    // idWidthForGui = getIdPanel().getWidth();
+    // } else {
+    // idWidthForGui=0;
+    // }
     /*
      * Get the horizontal offset to where we draw the sequences.
      * This is idWidth if using a single Graphics context, else zero.
@@ -994,6 +1123,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
     }
     final int alignmentDrawnHeight = (endSeq - startSeq) * charHeight + 3;
 
+    alignmentGraphics.setColor(Color.white);
+    alignmentGraphics.fillRect(0, 0, pageWidth, pageHeight + scaleHeight);
+
     /*
      * draw the Scale at horizontal offset, then reset to top left (0, 0)
      */
@@ -1010,8 +1142,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
     IdCanvas idCanvas = getIdPanel().getIdCanvas();
     List<SequenceI> selection = av.getSelectionGroup() == null ? null
             : av.getSelectionGroup().getSequences(null);
+
     idCanvas.drawIds((Graphics2D) idGraphics, av, startSeq, endSeq - 1,
-            selection);
+            selection, false, idWidth);
 
     idGraphics.setFont(av.getFont());
     idGraphics.translate(0, -scaleHeight);
@@ -1021,8 +1154,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
      * single graphics context), then reset to (0, scale height)
      */
     alignmentGraphics.translate(alignmentGraphicsOffset, scaleHeight);
-    getSeqPanel().seqCanvas.drawPanelForPrinting(alignmentGraphics, startRes,
-            endRes, startSeq, endSeq - 1);
+    getSeqPanel().seqCanvas.drawPanelForPrinting(alignmentGraphics,
+            startRes, endRes, startSeq, endSeq - 1);
     alignmentGraphics.translate(-alignmentGraphicsOffset, 0);
 
     if (av.isShowAnnotation() && (endSeq == alignmentHeight))
@@ -1035,7 +1168,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
       int offset = getAlabels().getScrollOffset();
       idGraphics.translate(0, -offset);
       idGraphics.translate(0, alignmentDrawnHeight);
-      getAlabels().drawComponent(idGraphics, idWidth);
+      getAlabels().drawComponentNotGUI(idGraphics, idWidth);
       idGraphics.translate(0, -alignmentDrawnHeight);
 
       /*
@@ -1044,6 +1177,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
        */
       alignmentGraphics.translate(alignmentGraphicsOffset,
               alignmentDrawnHeight);
+      updateLayout();
       getAnnotationPanel().renderer.drawComponent(getAnnotationPanel(), av,
               alignmentGraphics, -1, startRes, endRes + 1);
     }
@@ -1066,9 +1200,11 @@ public class AlignmentPanel extends GAlignmentPanel implements
    * 
    * @throws PrinterException
    */
-  public int printWrappedAlignment(int pageWidth, int pageHeight, int pageNumber,
-          Graphics g) throws PrinterException
+  public int printWrappedAlignment(int pageWidth, int pageHeight,
+          int pageNumber, Graphics g) throws PrinterException
   {
+    getSeqPanel().seqCanvas.calculateWrappedGeometry(getWidth(),
+            getHeight());
     int annotationHeight = 0;
     if (av.isShowAnnotation())
     {
@@ -1090,6 +1226,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
     int resWidth = getSeqPanel().seqCanvas
             .getWrappedCanvasWidth(pageWidth - idWidth);
+    av.getRanges().setViewportStartAndWidth(0, resWidth);
 
     int totalHeight = cHeight * (maxwidth / resWidth + 1);
 
@@ -1101,8 +1238,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
     /*
      * 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)
+     * single pages or ranges, (at the cost of repeated processing in the 
+     * 'normal' case, when all pages are printed)
      */
     g.translate(0, -pageNumber * pageHeight);
 
@@ -1112,12 +1249,12 @@ public class AlignmentPanel extends GAlignmentPanel implements
      * draw sequence ids and annotation labels (if shown)
      */
     IdCanvas idCanvas = getIdPanel().getIdCanvas();
-    idCanvas.drawIdsWrapped((Graphics2D) g, av, 0, totalHeight);
+    idCanvas.drawIdsWrappedNoGUI((Graphics2D) g, av, 0, totalHeight);
 
     g.translate(idWidth, 0);
 
-    getSeqPanel().seqCanvas.drawWrappedPanelForPrinting(g, pageWidth - idWidth,
-            totalHeight, 0);
+    getSeqPanel().seqCanvas.drawWrappedPanelForPrinting(g,
+            pageWidth - idWidth, totalHeight, 0);
 
     if ((pageNumber * pageHeight) < totalHeight)
     {
@@ -1149,103 +1286,83 @@ public class AlignmentPanel extends GAlignmentPanel implements
    *          be returned
    * @return
    */
-  public int getVisibleIdWidth(boolean onscreen)
+  protected int getVisibleIdWidth(boolean onscreen)
   {
     // see if rendering offscreen - check preferences and calc width accordingly
     if (!onscreen && Cache.getDefault("FIGURE_AUTOIDWIDTH", false))
     {
-      return calculateIdWidth(-1).width + 4;
+      return calculateIdWidth(-1, true, true).width;
     }
-    Integer idwidth = null;
-    if (onscreen || (idwidth = Cache
-            .getIntegerProperty("FIGURE_FIXEDIDWIDTH")) == null)
+    Integer idwidth = onscreen ? null
+            : Cache.getIntegerProperty("FIGURE_FIXEDIDWIDTH");
+    if (idwidth != null)
     {
-      int w = getIdPanel().getWidth();
-      return (w > 0 ? w : calculateIdWidth().width + 4);
+      return idwidth.intValue() + ID_WIDTH_PADDING;
     }
-    return idwidth.intValue() + 4;
+
+    int w = getIdPanel().getWidth();
+    w = calculateIdWidth(-1, true, true).width;
+    return (w > 0 ? w : calculateIdWidth().width);
   }
 
-  void makeAlignmentImage(jalview.util.ImageMaker.TYPE type, File file)
+  void makeAlignmentImage(ImageMaker.TYPE type, File file, String renderer)
+          throws ImageOutputException
   {
-    int boarderBottomOffset = 5;
-    long pSessionId = System.currentTimeMillis();
-    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);
-      }
-    }
-    try
+    makeAlignmentImage(type, file, renderer,
+            BitmapImageSizing.defaultBitmapImageSizing());
+  }
+
+  /**
+   * Builds an image of the alignment of the specified type (EPS/PNG/SVG) and
+   * writes it to the specified file
+   * 
+   * @param type
+   * @param file
+   * @param textrenderer
+   * @param bitmapscale
+   */
+  void makeAlignmentImage(ImageMaker.TYPE type, File file, String renderer,
+          BitmapImageSizing userBis) throws ImageOutputException
+  {
+    final int borderBottomOffset = 5;
+
+    AlignmentDimension aDimension = getAlignmentDimension();
+
+    // todo use a lambda function in place of callback here?
+    ImageWriterI writer = new ImageWriterI()
     {
-      AlignmentDimension aDimension = getAlignmentDimension();
-      try
+      @Override
+      public void exportImage(Graphics graphics) throws Exception
       {
-        jalview.util.ImageMaker im;
-        final String imageAction, imageTitle;
-        if (type == jalview.util.ImageMaker.TYPE.PNG)
-        {
-          imageAction = "Create PNG image from alignment";
-          imageTitle = null;
-        }
-        else if (type == jalview.util.ImageMaker.TYPE.EPS)
-        {
-          imageAction = "Create EPS file from alignment";
-          imageTitle = alignFrame.getTitle();
-        }
-        else
-        {
-          imageAction = "Create SVG file from alignment";
-          imageTitle = alignFrame.getTitle();
-        }
-
-        im = new jalview.util.ImageMaker(this, type, imageAction,
-                aDimension.getWidth(),
-                aDimension.getHeight() + boarderBottomOffset, file,
-                imageTitle, alignFrame, pSessionId, headless);
-        Graphics graphics = im.getGraphics();
         if (av.getWrapAlignment())
         {
-          if (graphics != null)
-          {
-            printWrappedAlignment(aDimension.getWidth(),
-                    aDimension.getHeight() + boarderBottomOffset, 0,
-                    graphics);
-            im.writeImage();
-          }
+          printWrappedAlignment(aDimension.getWidth(),
+                  aDimension.getHeight() + borderBottomOffset, 0, graphics);
         }
         else
         {
-          if (graphics != null)
-          {
-            printUnwrapped(aDimension.getWidth(), aDimension.getHeight(), 0,
-                    graphics, graphics);
-            im.writeImage();
-          }
+          printUnwrapped(aDimension.getWidth(), aDimension.getHeight(), 0,
+                  graphics, graphics);
         }
-
-      } catch (OutOfMemoryError err)
-      {
-        // Be noisy here.
-        System.out.println("########################\n" + "OUT OF MEMORY "
-                + file + "\n" + "########################");
-        new OOMWarning("Creating Image for " + file, err);
-        // System.out.println("Create IMAGE: " + err);
-      } catch (Exception ex)
-      {
-        ex.printStackTrace();
       }
-    } finally
-    {
+    };
 
-    }
+    String fileTitle = alignFrame.getTitle();
+    ImageExporter exporter = new ImageExporter(writer, alignFrame, type,
+            fileTitle);
+    int imageWidth = aDimension.getWidth();
+    int imageHeight = aDimension.getHeight() + borderBottomOffset;
+    String of = MessageManager.getString("label.alignment");
+    exporter.doExport(file, this, imageWidth, imageHeight, of, renderer,
+            userBis);
   }
 
+  /**
+   * Calculates and returns a suitable width and height (in pixels) for an
+   * exported image
+   * 
+   * @return
+   */
   public AlignmentDimension getAlignmentDimension()
   {
     int maxwidth = av.getAlignment().getVisibleWidth();
@@ -1257,7 +1374,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     if (av.getWrapAlignment())
     {
       height = getWrappedHeight();
-      if (headless)
+      if (Jalview.isHeadlessMode())
       {
         // need to obtain default alignment width and then add in any
         // additional allowance for id margin
@@ -1281,28 +1398,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
   }
 
-  /**
-   * DOCUMENT ME!
-   */
-  public void makeEPS(File epsFile)
-  {
-    makeAlignmentImage(jalview.util.ImageMaker.TYPE.EPS, epsFile);
-  }
-
-  /**
-   * DOCUMENT ME!
-   */
-  public void makePNG(File pngFile)
-  {
-    makeAlignmentImage(jalview.util.ImageMaker.TYPE.PNG, pngFile);
-  }
-
-  public void makeSVG(File svgFile)
-  {
-    makeAlignmentImage(jalview.util.ImageMaker.TYPE.SVG, svgFile);
-  }
-
   public void makePNGImageMap(File imgMapFile, String imageName)
+          throws ImageOutputException
   {
     // /////ONLY WORKS WITH NON WRAPPED ALIGNMENTS
     // ////////////////////////////////////////////
@@ -1336,13 +1433,13 @@ public class AlignmentPanel extends GAlignmentPanel implements
             String triplet = null;
             if (av.getAlignment().isNucleotide())
             {
-              triplet = ResidueProperties.nucleotideName.get(seq
-                      .getCharAt(column) + "");
+              triplet = ResidueProperties.nucleotideName
+                      .get(seq.getCharAt(column) + "");
             }
             else
             {
-              triplet = ResidueProperties.aa2Triplet.get(seq.getCharAt(column)
-                      + "");
+              triplet = ResidueProperties.aa2Triplet
+                      .get(seq.getCharAt(column) + "");
             }
 
             if (triplet == null)
@@ -1359,7 +1456,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
                 text.append("<area shape=\"rect\" coords=\"")
                         .append((idWidth + column * av.getCharWidth()))
                         .append(",").append(sy).append(",")
-                        .append((idWidth + (column + 1) * av.getCharWidth()))
+                        .append((idWidth
+                                + (column + 1) * av.getCharWidth()))
                         .append(",").append((av.getCharHeight() + sy))
                         .append("\"").append(" onMouseOver=\"toolTip('")
                         .append(seqPos).append(" ").append(triplet);
@@ -1385,7 +1483,8 @@ public class AlignmentPanel extends GAlignmentPanel implements
             }
             if (!Comparison.isGap(seq.getCharAt(column)))
             {
-              List<SequenceFeature> features = seq.findFeatures(column, column);
+              List<SequenceFeature> features = seq.findFeatures(column,
+                      column);
               for (SequenceFeature sf : features)
               {
                 if (sf.isContactFeature())
@@ -1425,18 +1524,24 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
       } catch (Exception ex)
       {
-        ex.printStackTrace();
+        throw new ImageOutputException(
+                "couldn't write ImageMap due to unexpected error", ex);
       }
     } // /////////END OF IMAGE MAP
 
   }
 
+  /**
+   * Answers the height of the entire alignment in pixels, assuming it is in
+   * wrapped mode
+   * 
+   * @return
+   */
   int getWrappedHeight()
   {
     int seqPanelWidth = getSeqPanel().seqCanvas.getWidth();
 
-    if (System.getProperty("java.awt.headless") != null
-            && System.getProperty("java.awt.headless").equals("true"))
+    if (Jalview.isHeadlessMode())
     {
       seqPanelWidth = alignFrame.getWidth() - getVisibleIdWidth()
               - vscroll.getPreferredSize().width
@@ -1455,6 +1560,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     int annotationHeight = 0;
     if (av.isShowAnnotation())
     {
+      hgap += SeqCanvas.SEQS_ANNOTATION_GAP;
       annotationHeight = getAnnotationPanel().adjustPanelHeight();
     }
 
@@ -1509,9 +1615,9 @@ public class AlignmentPanel extends GAlignmentPanel implements
     }
     else
     {
-      if (Cache.log.isDebugEnabled())
+      if (Console.isDebugEnabled())
       {
-        Cache.log.warn("Closing alignment panel which is already closed.");
+        Console.warn("Closing alignment panel which is already closed.");
       }
     }
   }
@@ -1586,13 +1692,17 @@ public class AlignmentPanel extends GAlignmentPanel implements
     } catch (Exception ex)
     {
     }
-
     if (b)
     {
-      alignFrame.setDisplayedView(this);
+      setAlignFrameView();
     }
   }
 
+  public void setAlignFrameView()
+  {
+    alignFrame.setDisplayedView(this);
+  }
+
   @Override
   public StructureSelectionManager getStructureSelectionManager()
   {
@@ -1781,4 +1891,42 @@ public class AlignmentPanel extends GAlignmentPanel implements
   {
     return calculationDialog;
   }
+
+  /**
+   * Constructs and sets the title for the Overview window (if there is one),
+   * including the align frame's title, and view name (if applicable). Returns
+   * the title, or null if this panel has no Overview window open.
+   * 
+   * @param alignFrame
+   * @return
+   */
+  public String setOverviewTitle(AlignFrame alignFrame)
+  {
+    if (this.overviewPanel == null)
+    {
+      return null;
+    }
+    String overviewTitle = MessageManager
+            .formatMessage("label.overview_params", new Object[]
+            { alignFrame.getTitle() });
+    String viewName = getViewName();
+    if (viewName != null)
+    {
+      overviewTitle += (" " + viewName);
+    }
+    overviewPanel.setTitle(overviewTitle);
+    return overviewTitle;
+  }
+
+  /**
+   * If this alignment panel has an Overview panel open, closes it
+   */
+  public void closeOverviewPanel()
+  {
+    if (overviewPanel != null)
+    {
+      overviewPanel.close();
+      overviewPanel = null;
+    }
+  }
 }