Merge branch 'feature/JAL-244_Automatically_adjust_left_margin_to_avoid_cropping_anno...
authorBen Soares <b.soares@dundee.ac.uk>
Fri, 18 Aug 2023 13:34:52 +0000 (14:34 +0100)
committerBen Soares <b.soares@dundee.ac.uk>
Fri, 18 Aug 2023 13:34:52 +0000 (14:34 +0100)
1  2 
src/jalview/bin/Commands.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/StructureChooser.java

@@@ -6,7 -6,6 +6,6 @@@ import java.net.URISyntaxException
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
- import java.util.EnumSet;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
@@@ -166,13 -165,9 +165,9 @@@ public class Command
      if (avm == null)
        return true;
  
-     /*
-      * // script to execute after all loading is completed one way or another String
-      * groovyscript = m.get(Arg.GROOVY) == null ? null :
-      * m.get(Arg.GROOVY).getValue(); String file = m.get(Arg.OPEN) == null ? null :
-      * m.get(Arg.OPEN).getValue(); String data = null; FileFormatI format = null;
-      * DataSourceType protocol = null;
-      */
+     // set wrap scope here so it can be applied after structures are opened
+     boolean wrap = false;
      if (avm.containsArg(Arg.APPEND) || avm.containsArg(Arg.OPEN))
      {
        commandArgsProvided = true;
            af = fileLoader.LoadFileWaitTillLoaded(openFile, protocol,
                    format);
  
-           // wrap alignment?
-           boolean wrap = ArgParser.getFromSubValArgOrPref(avm, Arg.WRAP, sv,
-                   null, "WRAP_ALIGNMENT", false);
-           af.getCurrentView().setWrapAlignment(wrap);
            // colour alignment?
            String colour = ArgParser.getFromSubValArgOrPref(avm, av,
                    Arg.COLOUR, sv, null, "DEFAULT_COLOUR_PROT", "");
                      false, false);
            }
  
+           // wrap alignment? do this last for formatting reasons
+           wrap = ArgParser.getFromSubValArgOrPref(avm, Arg.WRAP, sv, null,
+                   "WRAP_ALIGNMENT", false);
+           // af.setWrapFormat(wrap) is applied after structures are opened for
+           // annotation reasons
            // store the AlignFrame for this id
            afMap.put(id, af);
  
            String sViewer = ArgParser.getFromSubValArgOrPref(avm,
                    Arg.STRUCTUREVIEWER, Position.AFTER, av, subVals, null,
                    null, "jmol");
-           ViewerType viewerType = null;
-           if (!"none".equals(sViewer))
-           {
-             for (ViewerType v : EnumSet.allOf(ViewerType.class))
-             {
-               String name = v.name().toLowerCase(Locale.ROOT)
-                       .replaceAll(" ", "");
-               if (sViewer.equals(name))
-               {
-                 viewerType = v;
-                 break;
-               }
-             }
-           }
+           ViewerType viewerType = ViewerType.getFromString(sViewer);
  
            // TODO use ssFromStructure
            StructureViewer sv = StructureChooser
        }
      }
  
+     if (wrap)
+     {
+       AlignFrame af = afMap.get(id);
+       if (af != null)
+       {
+         af.setWrapFormat(wrap, true);
+       }
+     }
      /*
      boolean doShading = avm.getBoolean(Arg.TEMPFAC_SHADING);
      if (doShading)
          String val = av.getValue();
          SubVals subVals = av.getSubVals();
          String fileName = subVals.getContent();
 +        boolean stdout = ArgParser.STDOUTFILENAME.equals(fileName);
          File file = new File(fileName);
          boolean overwrite = ArgParser.getFromSubValArgOrPref(avm,
                  Arg.OVERWRITE, subVals, null, "OVERWRITE_OUTPUT", false);
                  !Platform.isHeadless());
  
          // if backups is not true then --overwrite must be specified
 -        if (file.exists() && !(overwrite || backups))
 +        if (file.exists() && !(overwrite || backups || stdout))
          {
            Console.error("Won't overwrite file '" + fileName + "' without "
                    + Arg.OVERWRITE.argString() + " or "
          }
          if (ff == null)
          {
 -          StringBuilder validSB = new StringBuilder();
 -          for (String f : validFormats)
 -          {
 -            if (validSB.length() > 0)
 -              validSB.append(", ");
 -            validSB.append(f);
 -            FileFormatI tff = ffs.forName(f);
 -            validSB.append(" (");
 -            validSB.append(tff.getExtensions());
 -            validSB.append(")");
 +          if (stdout)
 +          {
 +            ff = FileFormat.Fasta;
            }
 +          else
 +          {
 +            StringBuilder validSB = new StringBuilder();
 +            for (String f : validFormats)
 +            {
 +              if (validSB.length() > 0)
 +                validSB.append(", ");
 +              validSB.append(f);
 +              FileFormatI tff = ffs.forName(f);
 +              validSB.append(" (");
 +              validSB.append(tff.getExtensions());
 +              validSB.append(")");
 +            }
  
 -          Jalview.exit("No valid format specified for "
 -                  + Arg.OUTPUT.argString() + ". Valid formats are "
 -                  + validSB.toString() + ".", 1);
 -          // this return really shouldn't happen
 -          return false;
 +            Jalview.exit("No valid format specified for "
 +                    + Arg.OUTPUT.argString() + ". Valid formats are "
 +                    + validSB.toString() + ".", 1);
 +            // this return really shouldn't happen
 +            return false;
 +          }
          }
  
          String savedBackupsPreference = Cache
  
          Console.info("Writing " + fileName);
  
 -        af.saveAlignment(fileName, ff);
 +        af.saveAlignment(fileName, ff, stdout);
          Console.debug("Returning backups to " + savedBackupsPreference);
          if (savedBackupsPreference != null)
            Cache.applicationProperties.put(BackupFiles.ENABLED,
@@@ -49,7 -49,6 +49,7 @@@ import java.beans.PropertyChangeEvent
  import java.io.File;
  import java.io.FileWriter;
  import java.io.IOException;
 +import java.io.OutputStreamWriter;
  import java.io.PrintWriter;
  import java.net.URL;
  import java.util.ArrayList;
@@@ -624,12 -623,12 +624,12 @@@ public class AlignFrame extends GAlignF
          // if (viewport.cursorMode)
          // {
          // alignPanel.seqPanel.insertNucAtCursor(false,"A");
 -        // //System.out.println("A");
 +        // //jalview.bin.Console.outPrintln("A");
          // }
          // break;
          /*
           * case KeyEvent.VK_CLOSE_BRACKET: if (viewport.cursorMode) {
 -         * System.out.println("closing bracket"); } break;
 +         * jalview.bin.Console.outPrintln("closing bracket"); } break;
           */
          case KeyEvent.VK_DELETE:
          case KeyEvent.VK_BACK_SPACE:
                @Override
                public void propertyChange(PropertyChangeEvent evt)
                {
 -                // // System.out.println("Discoverer property change.");
 +                // // jalview.bin.Console.outPrintln("Discoverer property
 +                // change.");
                  // if (evt.getPropertyName().equals("services"))
                  {
                    SwingUtilities.invokeLater(new Runnable()
                      @Override
                      public void run()
                      {
 -                      System.err.println(
 +                      jalview.bin.Console.errPrintln(
                                "Rebuild WS Menu for service change");
                        BuildWebServiceMenu();
                      }
        public void internalFrameClosed(
                javax.swing.event.InternalFrameEvent evt)
        {
 -        // System.out.println("deregistering discoverer listener");
 +        // jalview.bin.Console.outPrintln("deregistering discoverer listener");
          Desktop.instance.removeJalviewPropertyChangeListener("services",
                  thisListener);
          closeMenuItem_actionPerformed(true);
     */
    public void saveAlignment(String file, FileFormatI format)
    {
 +    saveAlignment(file, format, false);
 +  }
 +
 +  public void saveAlignment(String file, FileFormatI format, boolean stdout)
 +  {
      lastSaveSuccessful = true;
 -    lastFilenameSaved = file;
 +    if (!stdout)
 +    {
 +      lastFilenameSaved = file;
 +    }
      lastFormatSaved = format;
  
      if (FileFormat.Jalview.equals(format))
          shortName = shortName
                  .substring(shortName.lastIndexOf(File.separatorChar) + 1);
        }
 +      // TODO deal with stdout=true
        lastSaveSuccessful = new Jalview2XML().saveAlignment(this, file,
                shortName);
  
        else
        {
          // create backupfiles object and get new temp filename destination
 -        boolean doBackup = BackupFiles.getEnabled();
 +        boolean doBackup = BackupFiles.getEnabled() && !stdout;
          BackupFiles backupfiles = null;
          if (doBackup)
          {
            String tempFilePath = doBackup ? backupfiles.getTempFilePath()
                    : file;
            Console.trace("ALIGNFRAME setting PrintWriter");
 -          PrintWriter out = new PrintWriter(new FileWriter(tempFilePath));
 +          PrintWriter out = stdout
 +                  ? new PrintWriter(new OutputStreamWriter(System.out))
 +                  : new PrintWriter(new FileWriter(tempFilePath));
  
            if (backupfiles != null)
            {
            }
  
            out.print(output);
 -          Console.trace("ALIGNFRAME about to close file");
 -          out.close();
 -          Console.trace("ALIGNFRAME closed file");
 -          AlignFrame.this.setTitle(file);
 -          statusBar.setText(MessageManager.formatMessage(
 -                  "label.successfully_saved_to_file_in_format", new Object[]
 -                  { fileName, format.getName() }));
 +          out.flush();
 +          if (!stdout)
 +          {
 +            Console.trace("ALIGNFRAME about to close file");
 +            out.close();
 +            Console.trace("ALIGNFRAME closed file");
 +          }
 +          AlignFrame.this.setTitle(stdout ? "STDOUT" : file);
 +          if (stdout)
 +          {
 +            statusBar.setText(MessageManager.formatMessage(
 +                    "label.successfully_printed_to_stdout_in_format",
 +                    new Object[]
 +                    { format.getName() }));
 +          }
 +          else
 +          {
 +            statusBar.setText(MessageManager.formatMessage(
 +                    "label.successfully_saved_to_file_in_format",
 +                    new Object[]
 +                    { fileName, format.getName() }));
 +          }
            lastSaveSuccessful = true;
          } catch (IOException e)
          {
      } catch (Exception ex)
      {
        ex.printStackTrace();
 -      System.out.println("Exception whilst pasting: " + ex);
 +      jalview.bin.Console.outPrintln("Exception whilst pasting: " + ex);
        // could be anything being pasted in here
      }
  
      } catch (Exception ex)
      {
        ex.printStackTrace();
 -      System.out.println("Exception whilst pasting: " + ex);
 +      jalview.bin.Console.outPrintln("Exception whilst pasting: " + ex);
        // could be anything being pasted in here
      } catch (OutOfMemoryError oom)
      {
    @Override
    public void wrapMenuItem_actionPerformed(ActionEvent e)
    {
-     scaleAbove.setVisible(wrapMenuItem.isSelected());
-     scaleLeft.setVisible(wrapMenuItem.isSelected());
-     scaleRight.setVisible(wrapMenuItem.isSelected());
-     viewport.setWrapAlignment(wrapMenuItem.isSelected());
+     setWrapFormat(wrapMenuItem.isSelected(), false);
+   }
+   public void setWrapFormat(boolean b, boolean setMenuItem)
+   {
+     scaleAbove.setVisible(b);
+     scaleLeft.setVisible(b);
+     scaleRight.setVisible(b);
+     viewport.setWrapAlignment(b);
      alignPanel.updateLayout();
+     if (setMenuItem)
+     {
+       wrapMenuItem.setSelected(b);
+     }
    }
  
    @Override
      {
        try
        {
 -        System.err.println("Waiting for building menu to finish.");
 +        jalview.bin.Console
 +                .errPrintln("Waiting for building menu to finish.");
          Thread.sleep(10);
        } catch (Exception e)
        {
          final List<JMenuItem> legacyItems = new ArrayList<>();
          try
          {
 -          // System.err.println("Building ws menu again "
 +          // jalview.bin.Console.errPrintln("Building ws menu again "
            // + Thread.currentThread());
            // TODO: add support for context dependent disabling of services based
            // on
                                      Desktop.instance);
                      if (pe != null)
                      {
 -                      System.err.println("Associated file : "
 +                      jalview.bin.Console.errPrintln("Associated file : "
                                + (fm[0].toString()) + " with "
                                + toassoc.getDisplayId(true));
                        assocfiles++;
                viewport.getAlignment()));
      } catch (Exception ex)
      {
 -      System.err.println(ex.getMessage());
 +      jalview.bin.Console.errPrintln(ex.getMessage());
        return;
      }
    }
          console.runScript();
        } catch (Exception ex)
        {
 -        System.err.println((ex.toString()));
 +        jalview.bin.Console.errPrintln((ex.toString()));
          JvOptionPane.showInternalMessageDialog(Desktop.desktop,
                  MessageManager.getString("label.couldnt_run_groovy_script"),
                  MessageManager.getString("label.groovy_support_failed"),
      }
      else
      {
 -      System.err.println("Can't run Groovy script as console not found");
 +      jalview.bin.Console
 +              .errPrintln("Can't run Groovy script as console not found");
      }
    }
  
@@@ -50,6 -50,7 +50,7 @@@ 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;
@@@ -269,10 -270,7 +270,7 @@@ public class AlignmentPanel extends GAl
      Dimension r = null;
      if (av.getIdWidth() < 0)
      {
-       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);
-       r = calculateIdWidth(maxwidth);
+       r = calculateDefaultAlignmentIdWidth();
        av.setIdWidth(r.width);
      }
      else
      return r;
    }
  
+   public Dimension calculateDefaultAlignmentIdWidth()
+   {
+     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);
+     return calculateIdWidth(-1, false, false);
+   }
    /**
     * Calculate the width of the alignment labels based on the displayed names
     * and any bounds on label width set in preferences.
     */
    protected Dimension calculateIdWidth(int maxwidth)
    {
+     return calculateIdWidth(maxwidth, true, false);
+   }
+   public Dimension calculateIdWidth(int maxwidth,
+           boolean includeAnnotations, boolean visibleOnly)
+   {
      Container c = new Container();
  
      FontMetrics fm = c.getFontMetrics(
      }
  
      // 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 (Jalview.isHeadlessMode())
        {
-         String label = al.getAlignmentAnnotation()[i].label;
-         int stringWidth = fm.stringWidth(label);
+         AnnotationLabels aal = this.getAlabels();
+         int stringWidth = aal.drawLabels(null, false, idWidth, false, fm);
          idWidth = Math.max(idWidth, stringWidth);
-         i++;
+       }
+       else
+       {
+         fm = c.getFontMetrics(getAlabels().getFont());
+         for (i = 0; i < al.getAlignmentAnnotation().length; i++)
+         {
+           AlignmentAnnotation aa = al.getAlignmentAnnotation()[i];
+           if (visibleOnly && !aa.visible)
+           {
+             continue;
+           }
+           String label = aa.label;
+           int stringWidth = fm.stringWidth(label);
+           idWidth = Math.max(idWidth, stringWidth);
+         }
        }
      }
  
      // this is called after loading new annotation onto alignment
      if (alignFrame.getHeight() == 0)
      {
 -      System.out.println("NEEDS FIXING");
 +      jalview.bin.Console.outPrintln("NEEDS FIXING");
      }
      validateAnnotationDimensions(true);
      addNotify();
      // not be called directly by programs.
      // I note that addNotify() is called in several areas of Jalview.
  
-     int annotationHeight = getAnnotationPanel().adjustPanelHeight();
-     annotationHeight = getAnnotationPanel()
-             .adjustForAlignFrame(adjustPanelHeight, annotationHeight);
+     AnnotationPanel ap = getAnnotationPanel();
+     int annotationHeight = ap.adjustPanelHeight();
+     annotationHeight = ap.adjustForAlignFrame(adjustPanelHeight,
+             annotationHeight);
  
      hscroll.addNotify();
-     annotationScroller.setPreferredSize(
-             new Dimension(annotationScroller.getWidth(), annotationHeight));
      Dimension e = idPanel.getSize();
-     alabels.setSize(new Dimension(e.width, annotationHeight));
+     int idWidth = e.width;
+     boolean manuallyAdjusted = this.getIdPanel().getIdCanvas()
+             .manuallyAdjusted();
+     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();
    }
  
    /**
      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)
      {
        }
      }
  
-     idSpaceFillerPanel1.setVisible(!wrap);
+     // idSpaceFillerPanel1.setVisible(!wrap);
  
      repaint();
    }
    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
  
      }
  
      int w = getIdPanel().getWidth();
+     w = this.calculateIdWidth(-1, true, true).width;
      return (w > 0 ? w : calculateIdWidth().width);
    }
  
-   void makeAlignmentImage(ImageMaker.TYPE type, File file, String renderer) throws ImageOutputException
+   void makeAlignmentImage(ImageMaker.TYPE type, File file, String renderer)
+           throws ImageOutputException
    {
      makeAlignmentImage(type, file, renderer,
              BitmapImageSizing.nullBitmapImageSizing());
      final int borderBottomOffset = 5;
  
      AlignmentDimension aDimension = getAlignmentDimension();
      // todo use a lambda function in place of callback here?
      ImageWriterI writer = new ImageWriterI()
      {
  
    }
  
-   public void makePNGImageMap(File imgMapFile, String imageName) throws ImageOutputException
+   public void makePNGImageMap(File imgMapFile, String imageName)
+           throws ImageOutputException
    {
      // /////ONLY WORKS WITH NON WRAPPED ALIGNMENTS
      // ////////////////////////////////////////////
  
        } catch (Exception ex)
        {
-         throw new ImageOutputException("couldn't write ImageMap due to unexpected error",ex);
+         throw new ImageOutputException(
+                 "couldn't write ImageMap due to unexpected error", ex);
        }
      } // /////////END OF IMAGE MAP
  
    {
      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
@@@ -20,6 -20,7 +20,7 @@@
   */
  package jalview.gui;
  
+ import java.awt.Canvas;
  import java.awt.Color;
  import java.awt.Cursor;
  import java.awt.Dimension;
@@@ -50,6 -51,8 +51,8 @@@ import javax.swing.ToolTipManager
  
  import jalview.analysis.AlignSeq;
  import jalview.analysis.AlignmentUtils;
+ import jalview.bin.Cache;
+ import jalview.bin.Jalview;
  import jalview.datamodel.Alignment;
  import jalview.datamodel.AlignmentAnnotation;
  import jalview.datamodel.Annotation;
@@@ -85,7 -88,7 +88,7 @@@ public class AnnotationLabels extends J
    /**
     * height in pixels for allowing height adjuster to be active
     */
-   private static int HEIGHT_ADJUSTER_HEIGHT = 10;
+   public static int HEIGHT_ADJUSTER_HEIGHT = 10;
  
    private static final Font font = new Font("Arial", Font.PLAIN, 11);
  
    private static final String COPYCONS_SEQ = MessageManager
            .getString("label.copy_consensus_sequence");
  
+   private static final String ADJUST_ANNOTATION_LABELS_WIDTH_PREF = "ADJUST_ANNOTATION_LABELS_WIDTH";
    private final boolean debugRedraw = false;
  
    private AlignmentPanel ap;
  
    private boolean resizePanel = false;
  
+   private int annotationIdWidth = -1;
+   public static final String RESIZE_MARGINS_MARK_PREF = "RESIZE_MARGINS_MARK";
    /**
     * Creates a new AnnotationLabels object
     * 
     */
    public AnnotationLabels(AlignmentPanel ap)
    {
      this.ap = ap;
      av = ap.av;
      ToolTipManager.sharedInstance().registerComponent(this);
          pop.add(consclipbrd);
        }
  
-       addColourOrFilterByOptions(ap,aa[selectedRow],pop);
-       
+       addColourOrFilterByOptions(ap, aa[selectedRow], pop);
        if (aa[selectedRow].graph == AlignmentAnnotation.CONTACT_MAP)
        {
-         addContactMatrixOptions(ap,aa[selectedRow],pop);
-           // Set/adjust threshold for grouping ?
-           // colour alignment by this [type]
-           // select/hide columns by this row
-           
-         }
+         addContactMatrixOptions(ap, aa[selectedRow], pop);
+         // Set/adjust threshold for grouping ?
+         // colour alignment by this [type]
+         // select/hide columns by this row
        }
-     
+     }
      pop.show(this, evt.getX(), evt.getY());
    }
  
    static void addColourOrFilterByOptions(final AlignmentPanel ap,
-           final AlignmentAnnotation alignmentAnnotation, final JPopupMenu pop)
+           final AlignmentAnnotation alignmentAnnotation,
+           final JPopupMenu pop)
    {
      JMenuItem item;
-     item = new JMenuItem(MessageManager.getString("label.colour_by_annotation"));
+     item = new JMenuItem(
+             MessageManager.getString("label.colour_by_annotation"));
      item.addActionListener(new ActionListener()
      {
-       
        @Override
        public void actionPerformed(ActionEvent e)
        {
-         AnnotationColourChooser.displayFor(ap.av, ap,alignmentAnnotation,false);
+         AnnotationColourChooser.displayFor(ap.av, ap, alignmentAnnotation,
+                 false);
        };
      });
      pop.add(item);
-     if (alignmentAnnotation.sequenceRef!=null)
+     if (alignmentAnnotation.sequenceRef != null)
      {
-       item = new JMenuItem(MessageManager.getString("label.colour_by_annotation")+" ("+MessageManager.getString("label.per_seq")+")");
+       item = new JMenuItem(
+               MessageManager.getString("label.colour_by_annotation") + " ("
+                       + MessageManager.getString("label.per_seq") + ")");
        item.addActionListener(new ActionListener()
        {
          @Override
          public void actionPerformed(ActionEvent e)
          {
-           AnnotationColourChooser.displayFor(ap.av, ap,alignmentAnnotation,true);
+           AnnotationColourChooser.displayFor(ap.av, ap, alignmentAnnotation,
+                   true);
          };
        });
        pop.add(item);
      }
-     item = new JMenuItem(MessageManager.getString("action.select_by_annotation"));
+     item = new JMenuItem(
+             MessageManager.getString("action.select_by_annotation"));
      item.addActionListener(new ActionListener()
      {
-       
        @Override
        public void actionPerformed(ActionEvent e)
        {
-         AnnotationColumnChooser.displayFor(ap.av,ap,alignmentAnnotation);
+         AnnotationColumnChooser.displayFor(ap.av, ap, alignmentAnnotation);
        };
      });
      pop.add(item);
    }
    static void addContactMatrixOptions(final AlignmentPanel ap,
-           final AlignmentAnnotation alignmentAnnotation, final JPopupMenu pop)
+           final AlignmentAnnotation alignmentAnnotation,
+           final JPopupMenu pop)
    {
-     
      final ContactMatrixI cm = ap.av.getContactMatrix(alignmentAnnotation);
      JMenuItem item;
      if (cm != null)
  
        if (cm.hasGroups())
        {
-         JCheckBoxMenuItem chitem = new JCheckBoxMenuItem(MessageManager.getString("action.show_groups_on_matrix"));
-         chitem.setToolTipText(MessageManager.getString("action.show_groups_on_matrix_tooltip"));
-         boolean showGroups = alignmentAnnotation.isShowGroupsForContactMatrix();
-         final AlignmentAnnotation sel_row=alignmentAnnotation;
+         JCheckBoxMenuItem chitem = new JCheckBoxMenuItem(
+                 MessageManager.getString("action.show_groups_on_matrix"));
+         chitem.setToolTipText(MessageManager
+                 .getString("action.show_groups_on_matrix_tooltip"));
+         boolean showGroups = alignmentAnnotation
+                 .isShowGroupsForContactMatrix();
+         final AlignmentAnnotation sel_row = alignmentAnnotation;
          chitem.setState(showGroups);
          chitem.addActionListener(new ActionListener()
          {
        }
        if (cm.hasTree())
        {
-         item = new JMenuItem(MessageManager.getString("action.show_tree_for_matrix"));
-         item.setToolTipText(MessageManager.getString("action.show_tree_for_matrix_tooltip"));
+         item = new JMenuItem(
+                 MessageManager.getString("action.show_tree_for_matrix"));
+         item.setToolTipText(MessageManager
+                 .getString("action.show_tree_for_matrix_tooltip"));
          item.addActionListener(new ActionListener()
          {
  
        }
        else
        {
-         item = new JMenuItem(MessageManager.getString("action.cluster_matrix"));
-         item.setToolTipText(MessageManager.getString("action.cluster_matrix_tooltip"));
+         item = new JMenuItem(
+                 MessageManager.getString("action.cluster_matrix"));
+         item.setToolTipText(
+                 MessageManager.getString("action.cluster_matrix_tooltip"));
          item.addActionListener(new ActionListener()
          {
            @Override
                public void run()
                {
                  final long progBar;
-                 ap.alignFrame.setProgressBar(MessageManager.formatMessage("action.clustering_matrix_for",cm.getAnnotDescr(),5f), progBar = System.currentTimeMillis());
+                 ap.alignFrame.setProgressBar(
+                         MessageManager.formatMessage(
+                                 "action.clustering_matrix_for",
+                                 cm.getAnnotDescr(), 5f),
+                         progBar = System.currentTimeMillis());
                  cm.setGroupSet(GroupSet.makeGroups(cm, true));
                  cm.randomlyReColourGroups();
                  cm.transferGroupColorsTo(alignmentAnnotation);
      }
  
      drawComponent(g2, true, width);
    }
  
    /**
     * @param width
     *          Width for scaling labels
     */
-   public void drawComponent(Graphics g, boolean clip, int width)
+   public void drawComponent(Graphics g, boolean clip, int givenWidth)
    {
-     if (av.getFont().getSize() < 10)
+     int width = givenWidth;
+     IdwidthAdjuster iwa = null;
+     if (ap != null)
      {
-       g.setFont(font);
+       iwa = ap.idwidthAdjuster;
+       if ((Cache.getDefault(ADJUST_ANNOTATION_LABELS_WIDTH_PREF, true)
+               || Jalview.isHeadlessMode()))
+       {
+         Graphics2D g2d = (Graphics2D) g;
+         Graphics dummy = g2d.create();
+         int newAnnotationIdWidth = drawLabels(dummy, clip, width, false,
+                 null);
+         dummy.dispose();
+         Dimension d = ap.calculateDefaultAlignmentIdWidth();
+         int alignmentIdWidth = d.width;
+         if (iwa != null && !iwa.manuallyAdjusted())
+         {
+           // If no manual adjustment to ID column with has been made then adjust
+           // width match widest of alignment or annotation id widths
+           boolean allowShrink = Cache.getDefault("ALLOW_SHRINK_ID_WIDTH",
+                   false);
+           width = Math.max(alignmentIdWidth, newAnnotationIdWidth);
+           if (clip && width < givenWidth && !allowShrink)
+           {
+             width = givenWidth;
+           }
+         }
+         else if (newAnnotationIdWidth != annotationIdWidth
+                 && newAnnotationIdWidth > givenWidth
+                 && newAnnotationIdWidth > alignmentIdWidth)
+         {
+           // otherwise if the annotation id width has become larger than the
+           // current id width, increase
+           width = newAnnotationIdWidth;
+           annotationIdWidth = newAnnotationIdWidth;
+         }
+         // set the width if it's changed
+         if (width != ap.av.getIdWidth())
+         {
+           iwa.setWidth(width);
+         }
+       }
      }
      else
      {
-       g.setFont(av.getFont());
+       int newAnnotationIdWidth = drawLabels(g, clip, width, false, null);
+       width = Math.max(newAnnotationIdWidth, givenWidth);
+     }
+     drawLabels(g, clip, width, true, null);
+   }
+   /**
+    * Render the full set of annotation Labels for the alignment at the given
+    * cursor. If actuallyDraw is false or g is null then no actual drawing will
+    * occur, but the widest label width will be returned. If g is null then
+    * fmetrics must be supplied.
+    * 
+    * Returns the width of the annotation labels.
+    * 
+    * @param g
+    *          Graphics2D instance (needed for font scaling)
+    * @param clip
+    *          - true indicates that only current visible area needs to be
+    *          rendered
+    * @param width
+    *          Width for scaling labels
+    * @param fmetrics
+    *          FontMetrics if Graphics object g is null
+    */
+   public int drawLabels(Graphics g0, boolean clip, int width,
+           boolean actuallyDraw, FontMetrics fmetrics)
+   {
+     if (clip)
+     {
+       clip = Cache.getDefault("MOVE_SEQUENCE_ID_WITH_VISIBLE_ANNOTATIONS",
+               true);
+     }
+     Graphics g = null;
+     // create a dummy Graphics object if not drawing and one is supplied
+     if (g0 != null)
+     {
+       if (!actuallyDraw)
+       {
+         Graphics2D g2d = (Graphics2D) g0;
+         g = g2d.create();
+       }
+       else
+       {
+         g = g0;
+       }
      }
+     int actualWidth = 0;
+     if (g != null)
+     {
+       if (av.getFont().getSize() < 10)
+       {
+         g.setFont(font);
+       }
+       else
+       {
+         g.setFont(av.getFont());
+       }
+     }
+     FontMetrics fm = fmetrics == null ? g.getFontMetrics(g.getFont())
+             : fmetrics;
+     if (actuallyDraw)
+     {
+       g.setColor(Color.white);
+       g.fillRect(0, 0, getWidth(), getHeight());
+       if (!Cache.getDefault(RESIZE_MARGINS_MARK_PREF, false)
+               && !av.getWrapAlignment())
+       {
+         g.setColor(Color.LIGHT_GRAY);
+         g.drawLine(0, HEIGHT_ADJUSTER_HEIGHT / 4, HEIGHT_ADJUSTER_WIDTH / 4,
+                 HEIGHT_ADJUSTER_HEIGHT / 4);
+         g.drawLine(0, 3 * HEIGHT_ADJUSTER_HEIGHT / 4,
+                 HEIGHT_ADJUSTER_WIDTH / 4, 3 * HEIGHT_ADJUSTER_HEIGHT / 4);
  
-     FontMetrics fm = g.getFontMetrics(g.getFont());
-     g.setColor(Color.white);
-     g.fillRect(0, 0, getWidth(), getHeight());
+       }
+     }
  
-     g.translate(0, getScrollOffset());
-     g.setColor(Color.black);
+     if (actuallyDraw)
+     {
+       g.translate(0, getScrollOffset());
+       g.setColor(Color.black);
+     }
      SequenceI lastSeqRef = null;
      String lastLabel = null;
      AlignmentAnnotation[] aa = av.getAlignment().getAlignmentAnnotation();
-     int fontHeight = g.getFont().getSize();
+     int fontHeight = g != null ? g.getFont().getSize()
+             : fm.getFont().getSize();
      int y = 0;
      int x = 0;
      int graphExtras = 0;
      int offset = 0;
-     Font baseFont = g.getFont();
+     Font baseFont = g != null ? g.getFont() : fm.getFont();
      FontMetrics baseMetrics = fm;
      int ofontH = fontHeight;
      int sOffset = 0;
              {
                if (debugRedraw)
                {
 -                System.out.println("before vis: " + i);
 +                jalview.bin.Console.outPrintln("before vis: " + i);
                }
                before = true;
              }
              {
                if (debugRedraw)
                {
 -                System.out.println(
 +                jalview.bin.Console.outPrintln(
                          "Scroll offset: " + sOffset + " after vis: " + i);
                }
                after = true;
              continue;
            }
          }
-         g.setColor(Color.black);
+         if (actuallyDraw && g != null)
+         {
+           g.setColor(Color.black);
+         }
          offset = -aa[i].height / 2;
  
          if (aa[i].hasText)
              vertBar = true;
            }
          }
-         x = width - fm.stringWidth(label) - 3;
+         int labelWidth = fm.stringWidth(label) + 3;
+         x = width - labelWidth;
  
          if (aa[i].graphGroup > -1)
          {
                s = ((float) fontHeight) / (float) ofontH;
                Font f = baseFont
                        .deriveFont(AffineTransform.getScaleInstance(s, s));
-               g.setFont(f);
-               fm = g.getFontMetrics();
-               graphExtras = (aa[i].height - (groupSize * (fontHeight + 8)))
-                       / 2;
+               Canvas c = new Canvas();
+               fm = c.getFontMetrics(f);
+               if (actuallyDraw && g != null)
+               {
+                 g.setFont(f);
+                 // fm = g.getFontMetrics();
+                 graphExtras = (aa[i].height
+                         - (groupSize * (fontHeight + 8))) / 2;
+               }
              }
            }
            if (visible)
              {
                if (aa[gg].graphGroup == aa[i].graphGroup)
                {
-                 x = width - fm.stringWidth(aa[gg].label) - 3;
-                 g.drawString(aa[gg].label, x, y - graphExtras);
-                 if (aa[gg]._linecolour != null)
+                 labelWidth = fm.stringWidth(aa[gg].label) + 3;
+                 x = width - labelWidth;
+                 if (actuallyDraw && g != null)
                  {
+                   g.drawString(aa[gg].label, x, y - graphExtras);
  
-                   g.setColor(aa[gg]._linecolour);
-                   g.drawLine(x, y - graphExtras + 3,
-                           x + fm.stringWidth(aa[gg].label),
-                           y - graphExtras + 3);
-                 }
+                   if (aa[gg]._linecolour != null)
+                   {
+                     g.setColor(aa[gg]._linecolour);
+                     g.drawLine(x, y - graphExtras + 3,
+                             x + fm.stringWidth(aa[gg].label),
+                             y - graphExtras + 3);
+                   }
  
-                 g.setColor(Color.black);
+                   g.setColor(Color.black);
+                 }
                  graphExtras += fontHeight + 8;
                }
              }
            }
-           g.setFont(baseFont);
+           if (actuallyDraw && g != null)
+           {
+             g.setFont(baseFont);
+           }
            fm = baseMetrics;
            fontHeight = ofontH;
          }
          else
          {
-           if (vertBar)
+           if (actuallyDraw && g != null)
            {
-             g.drawLine(width - 3, y + offset - fontHeight, width - 3,
-                     (int) (y - 1.5 * aa[i].height - offset - fontHeight));
-             // g.drawLine(20, y + offset, x - 20, y + offset);
+             if (vertBar)
+             {
+               g.drawLine(width - 3, y + offset - fontHeight, width - 3,
+                       (int) (y - 1.5 * aa[i].height - offset - fontHeight));
+               // g.drawLine(20, y + offset, x - 20, y + offset);
  
+             }
+             g.drawString(label, x, y + offset);
            }
-           g.drawString(label, x, y + offset);
          }
          lastSeqRef = aa[i].sequenceRef;
+         if (labelWidth > actualWidth)
+         {
+           actualWidth = labelWidth;
+         }
        }
      }
  
      if (!resizePanel && dragEvent != null && aa != null)
      {
-       g.setColor(Color.lightGray);
-       g.drawString(
-               (aa[selectedRow].sequenceRef == null ? ""
-                       : aa[selectedRow].sequenceRef.getName())
-                       + aa[selectedRow].label,
-               dragEvent.getX(), dragEvent.getY() - getScrollOffset());
+       if (actuallyDraw && g != null)
+       {
+         g.setColor(Color.lightGray);
+         g.drawString(
+                 (aa[selectedRow].sequenceRef == null ? ""
+                         : aa[selectedRow].sequenceRef.getName())
+                         + aa[selectedRow].label,
+                 dragEvent.getX(), dragEvent.getY() - getScrollOffset());
+       }
      }
  
      if (!av.getWrapAlignment() && ((aa == null) || (aa.length < 1)))
      {
-       g.drawString(MessageManager.getString("label.right_click"), 2, 8);
-       g.drawString(MessageManager.getString("label.to_add_annotation"), 2,
-               18);
+       if (actuallyDraw && g != null)
+       {
+         g.drawString(MessageManager.getString("label.right_click"), 2, 8);
+         g.drawString(MessageManager.getString("label.to_add_annotation"), 2,
+                 18);
+       }
      }
+     return actualWidth;
    }
  
    public int getScrollOffset()
@@@ -343,7 -343,7 +343,7 @@@ public class SeqCanvas extends JPanel i
          }
        }
  
 -      // System.err.println(">>> FastPaint to " + transX + " " + transY + " "
 +      // jalview.bin.Console.errPrintln(">>> FastPaint to " + transX + " " + transY + " "
        // + horizontal + " " + vertical + " " + startRes + " " + endRes
        // + " " + startSeq + " " + endSeq);
  
        // Call repaint on alignment panel so that repaints from other alignment
        // panel components can be aggregated. Otherwise performance of the
        // overview window and others may be adversely affected.
 -      // System.out.println("SeqCanvas fastPaint() repaint() request...");
 +      // jalview.bin.Console.outPrintln("SeqCanvas fastPaint() repaint() request...");
        av.getAlignPanel().repaint();
      } finally
      {
      labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
  
      return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
+   }
+   public int getMinimumWrappedCanvasWidth()
+   {
+     int charWidth = av.getCharWidth();
+     FontMetrics fm = getFontMetrics(av.getFont());
+     int labelWidth = 0;
+     if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
+     {
+       labelWidth = getLabelWidth(fm);
+     }
+     labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
+     labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
+     return labelWidthEast + labelWidthWest + charWidth;
    }
  
    /**
    public void propertyChange(PropertyChangeEvent evt)
    {
      String eventName = evt.getPropertyName();
 -    // System.err.println(">>SeqCanvas propertyChange " + eventName);
 +    // jalview.bin.Console.errPrintln(">>SeqCanvas propertyChange " + eventName);
      if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
      {
        fastPaint = true;
      else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
      {
        fastPaint = false;
 -      // System.err.println("!!!! fastPaint false from MOVE_VIEWPORT");
 +      // jalview.bin.Console.errPrintln("!!!! fastPaint false from MOVE_VIEWPORT");
        repaint();
        return;
      }
@@@ -71,7 -71,6 +71,6 @@@ import jalview.gui.structurechooser.PDB
  import jalview.gui.structurechooser.StructureChooserQuerySource;
  import jalview.gui.structurechooser.ThreeDBStructureChooserQuerySource;
  import jalview.io.DataSourceType;
- import jalview.io.FileFormatException;
  import jalview.io.JalviewFileChooser;
  import jalview.io.JalviewFileView;
  import jalview.jbgui.FilterOption;
@@@ -85,7 -84,6 +84,6 @@@ import jalview.util.StringUtils
  import jalview.ws.DBRefFetcher;
  import jalview.ws.DBRefFetcher.FetchFinishedListenerI;
  import jalview.ws.datamodel.alphafold.PAEContactMatrix;
- import jalview.ws.dbsources.EBIAlfaFold;
  import jalview.ws.seqfetcher.DbSourceProxy;
  import jalview.ws.sifts.SiftsSettings;
  
@@@ -1525,7 -1523,7 +1523,7 @@@ public class StructureChooser extends G
              // for moment, it will work fine as is because it is self-contained
              String searchTerm = text.toLowerCase(Locale.ROOT);
              searchTerm = searchTerm.split(":")[0];
 -            // System.out.println(">>>>> search term : " + searchTerm);
 +            // jalview.bin.Console.outPrintln(">>>>> search term : " + searchTerm);
              List<FTSDataColumnI> wantedFields = new ArrayList<>();
              FTSRestRequest pdbRequest = new FTSRestRequest();
              pdbRequest.setAllowEmptySeq(false);
      sc.mainFrame.dispose();
  
      if (showRefAnnotations)
+     {
        showReferenceAnnotationsForSequence(ap.alignFrame, seq);
+     }
  
      return sv;
    }