JAL-4285 Better checkFiles processing. Better dialog.
[jalview.git] / src / jalview / bin / Commands.java
index 90eb45c..ba72831 100644 (file)
@@ -6,7 +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;
@@ -14,11 +13,12 @@ import java.util.Locale;
 import java.util.Map;
 
 import jalview.analysis.AlignmentUtils;
+import jalview.api.structures.JalviewStructureDisplayI;
+import jalview.bin.Jalview.ExitCode;
 import jalview.bin.argparser.Arg;
 import jalview.bin.argparser.ArgParser;
 import jalview.bin.argparser.ArgParser.Position;
 import jalview.bin.argparser.ArgValue;
-import jalview.bin.argparser.ArgValues;
 import jalview.bin.argparser.ArgValuesMap;
 import jalview.bin.argparser.SubVals;
 import jalview.datamodel.AlignmentI;
@@ -26,25 +26,36 @@ import jalview.datamodel.SequenceI;
 import jalview.datamodel.annotations.AlphaFoldAnnotationRowBuilder;
 import jalview.gui.AlignFrame;
 import jalview.gui.AlignmentPanel;
+import jalview.gui.AppJmol;
 import jalview.gui.Desktop;
 import jalview.gui.Preferences;
 import jalview.gui.StructureChooser;
 import jalview.gui.StructureViewer;
 import jalview.gui.StructureViewer.ViewerType;
 import jalview.io.AppletFormatAdapter;
+import jalview.io.BackupFiles;
 import jalview.io.BioJsHTMLOutput;
 import jalview.io.DataSourceType;
 import jalview.io.FileFormat;
 import jalview.io.FileFormatException;
 import jalview.io.FileFormatI;
+import jalview.io.FileFormats;
 import jalview.io.FileLoader;
 import jalview.io.HtmlSvgOutput;
 import jalview.io.IdentifyFile;
+import jalview.io.NewickFile;
+import jalview.io.exceptions.ImageOutputException;
+import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ColourSchemeProperty;
 import jalview.structure.StructureImportSettings.TFType;
 import jalview.structure.StructureSelectionManager;
+import jalview.util.FileUtils;
 import jalview.util.HttpUtils;
+import jalview.util.ImageMaker;
+import jalview.util.ImageMaker.TYPE;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
+import jalview.util.imagemaker.BitmapImageSizing;
 
 public class Commands
 {
@@ -56,10 +67,14 @@ public class Commands
 
   private Map<String, AlignFrame> afMap;
 
+  private Map<String, List<StructureViewer>> svMap;
+
   private boolean commandArgsProvided = false;
 
   private boolean argsWereParsed = false;
 
+  private List<String> errors = new ArrayList<>();
+
   public Commands(ArgParser argparser, boolean headless)
   {
     this(Desktop.instance, argparser, headless);
@@ -70,17 +85,16 @@ public class Commands
     argParser = argparser;
     headless = h;
     desktop = d;
-    afMap = new HashMap<String, AlignFrame>();
-    if (argparser != null)
-    {
-      processArgs(argparser, headless);
-    }
+    afMap = new HashMap<>();
   }
 
-  private boolean processArgs(ArgParser argparser, boolean h)
+  protected boolean processArgs()
   {
-    argParser = argparser;
-    headless = h;
+    if (argParser == null)
+    {
+      return true;
+    }
+
     boolean theseArgsWereParsed = false;
 
     if (argParser != null && argParser.getLinkedIds() != null)
@@ -89,13 +103,35 @@ public class Commands
       {
         ArgValuesMap avm = argParser.getLinkedArgs(id);
         theseArgsWereParsed = true;
-        theseArgsWereParsed &= processLinked(id);
+        boolean processLinkedOkay = processLinked(id);
+        theseArgsWereParsed &= processLinkedOkay;
+
+        processGroovyScript(id);
+
+        // wait around until alignFrame isn't busy
+        AlignFrame af = afMap.get(id);
+        while (af != null && af.getViewport().isCalcInProgress())
+        {
+          try
+          {
+            Thread.sleep(25);
+          } catch (Exception q)
+          {
+          }
+          ;
+        }
+
         theseArgsWereParsed &= processImages(id);
 
+        if (processLinkedOkay)
+        {
+          theseArgsWereParsed &= processOutput(id);
+        }
+
         // close ap
         if (avm.getBoolean(Arg.CLOSE))
         {
-          AlignFrame af = afMap.get(id);
+          af = afMap.get(id);
           if (af != null)
           {
             af.closeMenuItem_actionPerformed(true);
@@ -105,9 +141,18 @@ public class Commands
       }
 
     }
-    if (argParser.getBool(Arg.QUIT))
+
+    // report errors
+    Console.warn(
+            "The following errors and warnings occurred whilst processing files:\n"
+                    + errorsToString());
+    // gui errors reported in Jalview
+
+    if (argParser.getBoolean(Arg.QUIT))
     {
-      Jalview.getInstance().quit();
+      Jalview.getInstance().exit(
+              "Exiting due to " + Arg.QUIT.argString() + " argument.",
+              ExitCode.OK);
       return true;
     }
     // carry on with jalview.bin.Jalview
@@ -125,25 +170,20 @@ public class Commands
     return argsWereParsed;
   }
 
-  protected boolean processUnlinked(String id)
-  {
-    return processLinked(id);
-  }
-
   protected boolean processLinked(String id)
   {
     boolean theseArgsWereParsed = false;
     ArgValuesMap avm = argParser.getLinkedArgs(id);
     if (avm == null)
+    {
       return true;
+    }
+
+    Boolean isError = Boolean.valueOf(false);
+
+    // set wrap scope here so it can be applied after structures are opened
+    boolean wrap = false;
 
-    /*
-     * // 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;
-     */
     if (avm.containsArg(Arg.APPEND) || avm.containsArg(Arg.OPEN))
     {
       commandArgsProvided = true;
@@ -192,7 +232,9 @@ public class Commands
           {
             if (!(new File(openFile)).exists())
             {
-              Console.warn("Can't find file '" + openFile + "'");
+              addError("Can't find file '" + openFile + "'");
+              isError = true;
+              continue;
             }
           }
         }
@@ -206,7 +248,9 @@ public class Commands
           format = new IdentifyFile().identify(openFile, protocol);
         } catch (FileFormatException e1)
         {
-          Console.error("Unknown file format for '" + openFile + "'");
+          addError("Unknown file format for '" + openFile + "'");
+          isError = true;
+          continue;
         }
 
         af = afMap.get(id);
@@ -214,68 +258,130 @@ public class Commands
         if (af == null || "true".equals(av.getSubVal("new"))
                 || a == Arg.OPEN || format == FileFormat.Jalview)
         {
-          /*
-           * this approach isn't working yet // get default annotations before opening
-           * AlignFrame if (m.get(Arg.SSANNOTATIONS) != null) {
-           * Console.debug("##### SSANNOTATIONS=" + m.get(Arg.SSANNOTATIONS).getBoolean());
-           * } if (m.get(Arg.NOTEMPFAC) != null) { Console.debug( "##### NOTEMPFAC=" +
-           * m.get(Arg.NOTEMPFAC).getBoolean()); } boolean showSecondaryStructure =
-           * (m.get(Arg.SSANNOTATIONS) != null) ? m.get(Arg.SSANNOTATIONS).getBoolean() :
-           * false; boolean showTemperatureFactor = (m.get(Arg.NOTEMPFAC) != null) ?
-           * !m.get(Arg.NOTEMPFAC).getBoolean() : false; Console.debug("##### tempfac=" +
-           * showTemperatureFactor + ", showSS=" + showSecondaryStructure);
-           * StructureSelectionManager ssm = StructureSelectionManager
-           * .getStructureSelectionManager(Desktop.instance); if (ssm != null) {
-           * ssm.setAddTempFacAnnot(showTemperatureFactor);
-           * ssm.setProcessSecondaryStructure(showSecondaryStructure); }
-           */
+          if (a == Arg.OPEN)
+          {
+            Jalview.testoutput(argParser, Arg.OPEN, "examples/uniref50.fa",
+                    openFile);
+          }
 
           Console.debug(
                   "Opening '" + openFile + "' in new alignment frame");
           FileLoader fileLoader = new FileLoader(!headless);
+          boolean xception = false;
+          try
+          {
+            af = fileLoader.LoadFileWaitTillLoaded(openFile, protocol,
+                    format);
+          } catch (Throwable thr)
+          {
+            xception = true;
+            addError("Couldn't open '" + openFile + "' as " + format + " "
+                    + thr.getLocalizedMessage()
+                    + " (Enable debug for full stack trace)");
+            isError = true;
+            Console.debug("Exception when opening '" + openFile + "'", thr);
+          } finally
+          {
+            if (af == null && !xception)
+            {
+              addInfo("Ignoring '" + openFile
+                      + "' - no alignment data found.");
+              continue;
+            }
+          }
 
-          af = fileLoader.LoadFileWaitTillLoaded(openFile, protocol,
-                  format);
-          boolean showAnnotations = ArgParser.getFromSubValArgOrPref(avm,
-                  Arg.ANNOTATIONS, av.getSubVals(), null,
-                  "SHOW_ANNOTATIONS", true);
-          af.setAnnotationsVisibility(showAnnotations, false, true);
-
-          // wrap alignment?
-          boolean wrap = ArgParser.getFromSubValArgOrPref(avm, Arg.WRAP, sv,
-                  null, "WRAP_ALIGNMENT", false);
-          af.getCurrentView().setWrapAlignment(wrap);
-
-          // colour aligment?
+          // colour alignment?
           String colour = ArgParser.getFromSubValArgOrPref(avm, av,
                   Arg.COLOUR, sv, null, "DEFAULT_COLOUR_PROT", "");
-
           if ("" != colour)
           {
-            af.changeColour_actionPerformed(colour);
+            ColourSchemeI cs = ColourSchemeProperty.getColourScheme(
+                    af.getViewport(), af.getViewport().getAlignment(),
+                    colour);
+
+            if (cs == null && !"None".equals(colour))
+            {
+              addWarn("Couldn't parse '" + colour + "' as a colourscheme.");
+            }
+            else
+            {
+              af.changeColour(cs);
+            }
+            Jalview.testoutput(argParser, Arg.COLOUR, "zappo", colour);
           }
 
-          // change alignment frame title
+          // Change alignment frame title
           String title = ArgParser.getFromSubValArgOrPref(avm, av,
                   Arg.TITLE, sv, null, null, null);
           if (title != null)
+          {
             af.setTitle(title);
+            Jalview.testoutput(argParser, Arg.TITLE, "test title", title);
+          }
 
-          // show secondary structure annotations?
-          boolean showSSAnnotations = ArgParser.getFromSubValArgOrPref(avm,
-                  Arg.SSANNOTATIONS, av.getSubVals(), null,
-                  "STRUCT_FROM_PDB", true);
-          if (avm.getBoolean(Arg.SSANNOTATIONS))
+          // Add features
+          String featuresfile = ArgParser.getValueFromSubValOrArg(avm, av,
+                  Arg.FEATURES, sv);
+          if (featuresfile != null)
           {
-            af.setAnnotationsVisibility(showSSAnnotations, true, false);
-            /*
-            AlignmentUtils.showOrHideSequenceAnnotations(
-                    af.getCurrentView().getAlignment(),
-                    Collections.singleton("Secondary Structure"), null,
-                    false, false);
-             */
+            af.parseFeaturesFile(featuresfile,
+                    AppletFormatAdapter.checkProtocol(featuresfile));
+            Jalview.testoutput(argParser, Arg.FEATURES,
+                    "examples/testdata/plantfdx.features", featuresfile);
+          }
+
+          // Add annotations from file
+          String annotationsfile = ArgParser.getValueFromSubValOrArg(avm,
+                  av, Arg.ANNOTATIONS, sv);
+          if (annotationsfile != null)
+          {
+            af.loadJalviewDataFile(annotationsfile, null, null, null);
+            Jalview.testoutput(argParser, Arg.ANNOTATIONS,
+                    "examples/testdata/plantfdx.annotations",
+                    annotationsfile);
+          }
+
+          // Set or clear the sortbytree flag
+          boolean sortbytree = ArgParser.getBoolFromSubValOrArg(avm,
+                  Arg.SORTBYTREE, sv);
+          if (sortbytree)
+          {
+            af.getViewport().setSortByTree(true);
+            Jalview.testoutput(argParser, Arg.SORTBYTREE);
           }
 
+          // Load tree from file
+          String treefile = ArgParser.getValueFromSubValOrArg(avm, av,
+                  Arg.TREE, sv);
+          if (treefile != null)
+          {
+            try
+            {
+              NewickFile nf = new NewickFile(treefile,
+                      AppletFormatAdapter.checkProtocol(treefile));
+              af.getViewport().setCurrentTree(
+                      af.showNewickTree(nf, treefile).getTree());
+              Jalview.testoutput(argParser, Arg.TREE,
+                      "examples/testdata/uniref50_test_tree", treefile);
+            } catch (IOException e)
+            {
+              addError("Couldn't add tree " + treefile, e);
+              isError = true;
+            }
+          }
+
+          // Show secondary structure annotations?
+          boolean showSSAnnotations = ArgParser.getFromSubValArgOrPref(avm,
+                  Arg.SHOWSSANNOTATIONS, av.getSubVals(), null,
+                  "STRUCT_FROM_PDB", true);
+          af.setAnnotationsVisibility(showSSAnnotations, true, false);
+
+          // Show sequence annotations?
+          boolean showAnnotations = ArgParser.getFromSubValArgOrPref(avm,
+                  Arg.SHOWANNOTATIONS, av.getSubVals(), null,
+                  "SHOW_ANNOTATIONS", true);
+          af.setAnnotationsVisibility(showAnnotations, false, true);
+
           // show temperature factor annotations?
           if (avm.getBoolean(Arg.NOTEMPFAC))
           {
@@ -287,33 +393,12 @@ public class Commands
                     af.getCurrentView().getAlignment(), hideThese, null,
                     false, false);
           }
-          else
-          /*
-           * comment out hacky approach up to here and add this line: if
-           * (showTemperatureFactor)
-           */
-          {
-            /*
-            if (avm.containsArg(Arg.TEMPFAC_LABEL))
-            {
-              AlignmentAnnotation aa = AlignmentUtils
-                      .getFirstSequenceAnnotationOfType(
-                              af.getCurrentView().getAlignment(),
-                              AlignmentAnnotation.LINE_GRAPH);
-              String label = avm.getValue(Arg.TEMPFAC_LABEL);
-              if (aa != null)
-              {
-                aa.label = label;
-              }
-              else
-              {
-                Console.info(
-                        "Could not find annotation to apply tempfac_label '"
-                                + label);
-              }
-            }
-            */
-          }
+
+          // 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);
@@ -347,11 +432,12 @@ public class Commands
       {
         if (headless)
         {
-          Jalview.exit("Could not open any files in headless mode", 1);
+          Jalview.exit("Could not open any files in headless mode",
+                  ExitCode.NO_FILES);
         }
         else
         {
-          Console.warn("No more files to open");
+          Console.info("No more files to open");
         }
       }
       if (progressBarSet && desktop != null)
@@ -370,7 +456,7 @@ public class Commands
         {
           String val = av.getValue();
           SubVals subVals = av.getSubVals();
-          SequenceI seq = getSpecifiedSequence(af, subVals);
+          SequenceI seq = getSpecifiedSequence(af, avm, av);
           if (seq == null)
           {
             // Could not find sequence from subId, let's assume the first
@@ -381,10 +467,8 @@ public class Commands
 
           if (seq == null)
           {
-            Console.warn("Could not find sequence for argument "
+            addWarn("Could not find sequence for argument "
                     + Arg.STRUCTURE.argString() + "=" + val);
-            // you probably want to continue here, not break
-            // break;
             continue;
           }
           File structureFile = null;
@@ -417,28 +501,20 @@ public class Commands
 
           if (structureFile == null)
           {
-            Console.warn("Not provided structure file with '" + val + "'");
+            addWarn("Not provided structure file with '" + val + "'");
             continue;
           }
 
           if (!structureFile.exists())
           {
-            Console.warn("Structure file '"
-                    + structureFile.getAbsoluteFile() + "' not found.");
+            addWarn("Structure file '" + structureFile.getAbsoluteFile()
+                    + "' not found.");
             continue;
           }
 
           Console.debug("Using structure file "
                   + structureFile.getAbsolutePath());
 
-          // ##### Does this need to happen? Follow
-          // openStructureFileForSequence() below
-          /*
-          PDBEntry fileEntry = new AssociatePdbFileWithSeq()
-                  .associatePdbWithSeq(structureFile.getAbsolutePath(),
-                          DataSourceType.FILE, seq, true, Desktop.instance);
-                          */
-
           // open structure view
           AlignmentPanel ap = af.alignPanel;
           if (headless)
@@ -450,45 +526,41 @@ public class Commands
           String structureFilepath = structureFile.getAbsolutePath();
 
           // get PAEMATRIX file and label from subvals or Arg.PAEMATRIX
-          String paeFilepath = subVals.getWithSubstitutions(argParser, id,
-                  "paematrix");
-          String paeLabel = subVals.get("paelabel");
-          ArgValue paeAv = getArgAssociatedWithStructure(Arg.PAEMATRIX, avm,
-                  af, structureFilepath);
-          if (paeFilepath == null && paeAv != null)
+          String paeFilepath = ArgParser
+                  .getFromSubValArgOrPrefWithSubstitutions(argParser, avm,
+                          Arg.PAEMATRIX, Position.AFTER, av, subVals, null,
+                          null, null);
+          if (paeFilepath != null)
           {
-            SubVals sv = paeAv.getSubVals();
-            File paeFile = new File(sv.getContent());
+            File paeFile = new File(paeFilepath);
 
-            paeLabel = sv.get("label");
             try
             {
               paeFilepath = paeFile.getCanonicalPath();
             } catch (IOException e)
             {
               paeFilepath = paeFile.getAbsolutePath();
-              Console.warn("Problem with the PAE file path: '"
+              addWarn("Problem with the PAE file path: '"
                       + paeFile.getPath() + "'");
             }
           }
 
           // showing annotations from structure file or not
           boolean ssFromStructure = ArgParser.getFromSubValArgOrPref(avm,
-                  Arg.SSANNOTATIONS, subVals, null, "STRUCT_FROM_PDB",
+                  Arg.SHOWSSANNOTATIONS, subVals, null, "STRUCT_FROM_PDB",
                   true);
 
           // get TEMPFAC type from subvals or Arg.TEMPFAC in case user Adds
           // reference annotations
-          String tftString = subVals.get("tempfac");
-          TFType tft = avm.getBoolean(Arg.NOTEMPFAC) ? null
-                  : TFType.DEFAULT;
-          ArgValue tftAv = getArgAssociatedWithStructure(Arg.TEMPFAC, avm,
-                  af, structureFilepath);
-          if (tftString == null && tftAv != null)
-          {
-            tftString = tftAv.getSubVals().getContent();
-          }
-          if (tftString != null)
+          String tftString = ArgParser
+                  .getFromSubValArgOrPrefWithSubstitutions(argParser, avm,
+                          Arg.TEMPFAC, Position.AFTER, av, subVals, null,
+                          null, null);
+          boolean notempfac = ArgParser.getFromSubValArgOrPref(avm,
+                  Arg.NOTEMPFAC, subVals, null, "ADD_TEMPFACT_ANN", false,
+                  true);
+          TFType tft = notempfac ? null : TFType.DEFAULT;
+          if (tftString != null && !notempfac)
           {
             // get kind of temperature factor annotation
             try
@@ -511,39 +583,159 @@ public class Commands
                 if (it.hasNext())
                   sb.append(", ");
               }
-              Console.warn(sb.toString());
+              addWarn(sb.toString());
             }
           }
 
           String sViewer = ArgParser.getFromSubValArgOrPref(avm,
-                  Arg.STRUCTUREVIEWER, Position.AFTER, av, subVals,
-                  "viewer", null, "jmol");
-          ViewerType viewerType = null;
-          if (!"none".equals(sViewer))
+                  Arg.STRUCTUREVIEWER, Position.AFTER, av, subVals, null,
+                  null, "jmol");
+          ViewerType viewerType = ViewerType.getFromString(sViewer);
+
+          // TODO use ssFromStructure
+          StructureViewer sv = StructureChooser
+                  .openStructureFileForSequence(null, null, ap, seq, false,
+                          structureFilepath, tft, paeFilepath, false,
+                          ssFromStructure, false, viewerType);
+
+          if (sv == null)
           {
-            for (ViewerType v : EnumSet.allOf(ViewerType.class))
+            addError("Failed to import and open structure view for file '"
+                    + structureFile + "'.");
+            continue;
+          }
+          try
+          {
+            long tries = 1000;
+            while (sv.isBusy() && tries > 0)
             {
-              String name = v.name().toLowerCase(Locale.ROOT)
-                      .replaceAll(" ", "");
-              if (sViewer.equals(name))
+              Thread.sleep(25);
+              if (sv.isBusy())
               {
-                viewerType = v;
-                break;
+                tries--;
+                Console.debug(
+                        "Waiting for viewer for " + structureFilepath);
               }
             }
+            if (tries == 0 && sv.isBusy())
+            {
+              addWarn("Gave up waiting for structure viewer to load file '"
+                      + structureFile
+                      + "'. Something may have gone wrong.");
+            }
+          } catch (Exception x)
+          {
+            addError("Exception whilst waiting for structure viewer "
+                    + structureFilepath, x);
+            isError = true;
           }
 
-          boolean addTempFac = tft != null
-                  || Cache.getDefault("ADD_TEMPFACT_ANN", false);
+          // add StructureViewer to svMap list
+          if (svMap == null)
+          {
+            svMap = new HashMap<>();
+          }
+          if (svMap.get(id) == null)
+          {
+            svMap.put(id, new ArrayList<>());
+          }
+          svMap.get(id).add(sv);
 
-          // TODO use ssFromStructure
-          StructureChooser.openStructureFileForSequence(null, null, ap, seq,
-                  false, structureFilepath, tft, paeFilepath, false,
-                  ssFromStructure, false, viewerType);
+          Console.debug(
+                  "Successfully opened viewer for " + structureFilepath);
+          String structureImageFilename = ArgParser.getValueFromSubValOrArg(
+                  avm, av, Arg.STRUCTUREIMAGE, subVals);
+          if (sv != null && structureImageFilename != null)
+          {
+            ArgValue siAv = avm.getClosestNextArgValueOfArg(av,
+                    Arg.STRUCTUREIMAGE);
+            SubVals sisv = null;
+            if (structureImageFilename.equals(siAv.getValue()))
+            {
+              sisv = siAv.getSubVals();
+            }
+            File structureImageFile = new File(structureImageFilename);
+            String width = ArgParser.getValueFromSubValOrArg(avm, av,
+                    Arg.STRUCTUREIMAGEWIDTH, sisv);
+            String height = ArgParser.getValueFromSubValOrArg(avm, av,
+                    Arg.STRUCTUREIMAGEHEIGHT, sisv);
+            String scale = ArgParser.getValueFromSubValOrArg(avm, av,
+                    Arg.STRUCTUREIMAGESCALE, sisv);
+            String renderer = ArgParser.getValueFromSubValOrArg(avm, av,
+                    Arg.STRUCTUREIMAGETEXTRENDERER, sisv);
+            String typeS = ArgParser.getValueFromSubValOrArg(avm, av,
+                    Arg.STRUCTUREIMAGETYPE, sisv);
+            if (typeS == null || typeS.length() == 0)
+            {
+              typeS = FileUtils.getExtension(structureImageFile);
+            }
+            TYPE imageType;
+            try
+            {
+              imageType = Enum.valueOf(TYPE.class,
+                      typeS.toUpperCase(Locale.ROOT));
+            } catch (IllegalArgumentException e)
+            {
+              addWarn("Do not know image format '" + typeS
+                      + "', using PNG");
+              imageType = TYPE.PNG;
+            }
+            BitmapImageSizing userBis = ImageMaker
+                    .parseScaleWidthHeightStrings(scale, width, height);
+            // TODO MAKE THIS VIEWER INDEPENDENT!!
+            switch (StructureViewer.getViewerType())
+            {
+            case JMOL:
+              JalviewStructureDisplayI sview = sv
+                      .getJalviewStructureDisplay();
+              if (sview instanceof AppJmol)
+              {
+                AppJmol jmol = (AppJmol) sview;
+                try
+                {
+                  boolean success = this.checksBeforeWritingToFile(avm,
+                          subVals, false, structureImageFilename,
+                          "structure image", isError);
+                  if (!success)
+                  {
+                    continue;
+                  }
+
+                  Console.debug("Rendering image to " + structureImageFile);
+                  jmol.makePDBImage(structureImageFile, imageType, renderer,
+                          userBis);
+                  Console.debug("Finished Rendering image to "
+                          + structureImageFile);
+
+                } catch (ImageOutputException ioexc)
+                {
+                  addError("Unexpected error whilst exporting image to "
+                          + structureImageFile, ioexc);
+                  isError = true;
+                  continue;
+                }
+
+              }
+              break;
+            default:
+              addWarn("Cannot export image for structure viewer "
+                      + sv.getViewerType() + " yet");
+              continue;
+            }
+          }
         }
       }
     }
 
+    if (wrap)
+    {
+      AlignFrame af = afMap.get(id);
+      if (af != null)
+      {
+        af.setWrapFormat(wrap, true);
+      }
+    }
+
     /*
     boolean doShading = avm.getBoolean(Arg.TEMPFAC_SHADING);
     if (doShading)
@@ -561,7 +753,31 @@ public class Commands
     }
     */
 
-    return theseArgsWereParsed;
+    return theseArgsWereParsed && !isError;
+  }
+
+  protected void processGroovyScript(String id)
+  {
+    ArgValuesMap avm = argParser.getLinkedArgs(id);
+    AlignFrame af = afMap.get(id);
+
+    if (af == null)
+    {
+      addWarn("Did not have an alignment window for id=" + id);
+      return;
+    }
+
+    if (avm.containsArg(Arg.GROOVY))
+    {
+      String groovyscript = avm.getValue(Arg.GROOVY);
+      if (groovyscript != null)
+      {
+        // Execute the groovy script after we've done all the rendering stuff
+        // and before any images or figures are generated.
+        Console.info("Executing script " + groovyscript);
+        Jalview.getInstance().executeGroovyScript(groovyscript, af);
+      }
+    }
   }
 
   protected boolean processImages(String id)
@@ -571,23 +787,35 @@ public class Commands
 
     if (af == null)
     {
-      Console.warn("Did not have an alignment window for id=" + id);
+      addWarn("Did not have an alignment window for id=" + id);
       return false;
     }
 
+    Boolean isError = Boolean.valueOf(false);
     if (avm.containsArg(Arg.IMAGE))
     {
       for (ArgValue av : avm.getArgValueList(Arg.IMAGE))
       {
         String val = av.getValue();
         SubVals subVal = av.getSubVals();
-        String type = "png"; // default
         String fileName = subVal.getContent();
         File file = new File(fileName);
+        String name = af.getName();
         String renderer = ArgParser.getValueFromSubValOrArg(avm, av,
-                Arg.RENDERER, subVal);
+                Arg.TEXTRENDERER, subVal);
         if (renderer == null)
           renderer = "text";
+        String type = "png"; // default
+
+        String scale = ArgParser.getValueFromSubValOrArg(avm, av, Arg.SCALE,
+                subVal);
+        String width = ArgParser.getValueFromSubValOrArg(avm, av, Arg.WIDTH,
+                subVal);
+        String height = ArgParser.getValueFromSubValOrArg(avm, av,
+                Arg.HEIGHT, subVal);
+        BitmapImageSizing userBis = ImageMaker
+                .parseScaleWidthHeightStrings(scale, width, height);
+
         type = ArgParser.getValueFromSubValOrArg(avm, av, Arg.TYPE, subVal);
         if (type == null && fileName != null)
         {
@@ -603,113 +831,336 @@ public class Commands
         Cache.setPropsAreReadOnly(true);
         Cache.setProperty("EXPORT_EMBBED_BIOJSON", "false");
 
-        switch (type)
-        {
+        Console.info("Writing " + file);
 
-        case "svg":
-          Console.debug("Outputting type '" + type + "' to " + fileName);
-          af.createSVG(file, renderer);
-          break;
-
-        case "png":
-          Console.debug("Outputting type '" + type + "' to " + fileName);
-          af.createPNG(file);
-          break;
-
-        case "html":
-          Console.debug("Outputting type '" + type + "' to " + fileName);
-          HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
-          htmlSVG.exportHTML(fileName, renderer);
-          break;
+        boolean success = checksBeforeWritingToFile(avm, subVal, false,
+                fileName, "image", isError);
+        if (!success)
+        {
+          continue;
+        }
 
-        case "biojs":
-          try
-          {
-            BioJsHTMLOutput.refreshVersionInfo(
-                    BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
-          } catch (URISyntaxException e)
+        try
+        {
+          switch (type)
           {
-            e.printStackTrace();
-          }
-          BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
-          bjs.exportHTML(fileName);
-          Console.debug("Creating BioJS MSA Viwer HTML file: " + fileName);
-          break;
 
-        default:
-          Console.warn(Arg.IMAGE.argString() + " type '" + type
-                  + "' not known. Ignoring");
-          break;
+          case "svg":
+            Console.debug("Outputting type '" + type + "' to " + fileName);
+            af.createSVG(file, renderer);
+            break;
+
+          case "png":
+            Console.debug("Outputting type '" + type + "' to " + fileName);
+            af.createPNG(file, null, userBis);
+            break;
+
+          case "html":
+            Console.debug("Outputting type '" + type + "' to " + fileName);
+            HtmlSvgOutput htmlSVG = new HtmlSvgOutput(af.alignPanel);
+            htmlSVG.exportHTML(fileName, renderer);
+            break;
+
+          case "biojs":
+            Console.debug(
+                    "Outputting BioJS MSA Viwer HTML file: " + fileName);
+            try
+            {
+              BioJsHTMLOutput.refreshVersionInfo(
+                      BioJsHTMLOutput.BJS_TEMPLATES_LOCAL_DIRECTORY);
+            } catch (URISyntaxException e)
+            {
+              e.printStackTrace();
+            }
+            BioJsHTMLOutput bjs = new BioJsHTMLOutput(af.alignPanel);
+            bjs.exportHTML(fileName);
+            break;
+
+          case "eps":
+            Console.debug("Outputting EPS file: " + fileName);
+            af.createEPS(file, renderer);
+            break;
+
+          case "imagemap":
+            Console.debug("Outputting ImageMap file: " + fileName);
+            af.createImageMap(file, name);
+            break;
+
+          default:
+            addWarn(Arg.IMAGE.argString() + " type '" + type
+                    + "' not known. Ignoring");
+            break;
+          }
+        } catch (Exception ioex)
+        {
+          addError("Unexpected error during export to '" + fileName + "'",
+                  ioex);
+          isError = true;
         }
       }
     }
-    return true;
+    return !isError;
   }
 
-  private SequenceI getSpecifiedSequence(AlignFrame af, SubVals subId)
+  protected boolean processOutput(String id)
   {
-    if (subId == null)
-      return null;
-    AlignmentI al = af.getCurrentView().getAlignment();
-    if (subId.has("seqid"))
-    {
-      return al.findName(subId.get("seqid"));
-    }
-    else if (-1 < subId.getIndex()
-            && subId.getIndex() < al.getSequences().size())
+    ArgValuesMap avm = argParser.getLinkedArgs(id);
+    AlignFrame af = afMap.get(id);
+
+    if (af == null)
     {
-      return al.getSequenceAt(subId.getIndex());
+      addWarn("Did not have an alignment window for id=" + id);
+      return false;
     }
-    return null;
-  }
 
-  // returns the first Arg value intended for the structure structFilename
-  // (in the given AlignFrame from the ArgValuesMap)
-  private ArgValue getArgAssociatedWithStructure(Arg arg, ArgValuesMap avm,
-          AlignFrame af, String structFilename)
-  {
-    if (af != null)
+    Boolean isError = Boolean.valueOf(false);
+
+    if (avm.containsArg(Arg.OUTPUT))
     {
-      for (ArgValue av : avm.getArgValueList(arg))
+      for (ArgValue av : avm.getArgValueList(Arg.OUTPUT))
       {
+        String val = av.getValue();
         SubVals subVals = av.getSubVals();
-        String structid = subVals.get("structid");
-        String structfile = subVals.get("structfile");
+        String fileName = subVals.getContent();
+        boolean stdout = ArgParser.STDOUTFILENAME.equals(fileName);
+        File file = new File(fileName);
 
-        // let's find a structure
-        if (structfile == null && structid == null)
+        String name = af.getName();
+        String format = ArgParser.getValueFromSubValOrArg(avm, av,
+                Arg.FORMAT, subVals);
+        FileFormats ffs = FileFormats.getInstance();
+        List<String> validFormats = ffs.getWritableFormats(false);
+
+        FileFormatI ff = null;
+        if (format == null && fileName != null)
         {
-          ArgValue likelyStructure = avm.getClosestPreviousArgValueOfArg(av,
-                  Arg.STRUCTURE);
-          if (likelyStructure != null)
+          FORMAT: for (String fname : validFormats)
           {
-            SubVals sv = likelyStructure.getSubVals();
-            if (sv != null && sv.has(ArgValues.ID))
+            FileFormatI tff = ffs.forName(fname);
+            String[] extensions = tff.getExtensions().split(",");
+            for (String ext : extensions)
             {
-              structid = sv.get(ArgValues.ID);
+              if (fileName.toLowerCase(Locale.ROOT).endsWith("." + ext))
+              {
+                ff = tff;
+                format = ff.getName();
+                break FORMAT;
+              }
             }
-            else
+          }
+        }
+        if (ff == null && format != null)
+        {
+          ff = ffs.forName(format);
+        }
+        if (ff == null)
+        {
+          if (stdout)
+          {
+            ff = FileFormat.Fasta;
+          }
+          else
+          {
+            StringBuilder validSB = new StringBuilder();
+            for (String f : validFormats)
             {
-              structfile = likelyStructure.getValue();
+              if (validSB.length() > 0)
+                validSB.append(", ");
+              validSB.append(f);
+              FileFormatI tff = ffs.forName(f);
+              validSB.append(" (");
+              validSB.append(tff.getExtensions());
+              validSB.append(")");
             }
+
+            addError("No valid format specified for "
+                    + Arg.OUTPUT.argString() + ". Valid formats are "
+                    + validSB.toString() + ".");
+            continue;
           }
         }
 
-        if (structfile == null && structid != null)
+        boolean success = checksBeforeWritingToFile(avm, subVals, true,
+                fileName, ff.getName(), isError);
+        if (!success)
         {
-          StructureSelectionManager ssm = StructureSelectionManager
-                  .getStructureSelectionManager(Desktop.instance);
-          if (ssm != null)
-          {
-            structfile = ssm.findFileForPDBId(structid);
-          }
+          continue;
+        }
+
+        boolean backups = ArgParser.getFromSubValArgOrPref(avm, Arg.BACKUPS,
+                subVals, null,
+                Platform.isHeadless() ? null : BackupFiles.ENABLED,
+                !Platform.isHeadless());
+
+        Console.info("Writing " + fileName);
+
+        af.saveAlignment(fileName, ff, stdout, backups);
+        if (af.isSaveAlignmentSuccessful())
+        {
+          Console.debug("Written alignment '" + name + "' in "
+                  + ff.getName() + " format to '" + file + "'");
+        }
+        else
+        {
+          addError("Error writing file '" + file + "' in " + ff.getName()
+                  + " format!");
+          isError = true;
+          continue;
         }
-        if (structfile != null && structfile.equals(structFilename))
+
+      }
+    }
+    return !isError;
+  }
+
+  private SequenceI getSpecifiedSequence(AlignFrame af, ArgValuesMap avm,
+          ArgValue av)
+  {
+    SubVals subVals = av.getSubVals();
+    ArgValue idAv = avm.getClosestNextArgValueOfArg(av, Arg.SEQID);
+    SequenceI seq = null;
+    if (subVals == null && idAv == null)
+      return null;
+    if (af == null || af.getCurrentView() == null)
+    {
+      return null;
+    }
+    AlignmentI al = af.getCurrentView().getAlignment();
+    if (al == null)
+    {
+      return null;
+    }
+    if (subVals != null)
+    {
+      if (subVals.has(Arg.SEQID.getName()))
+      {
+        seq = al.findName(subVals.get(Arg.SEQID.getName()));
+      }
+      else if (-1 < subVals.getIndex()
+              && subVals.getIndex() < al.getSequences().size())
+      {
+        seq = al.getSequenceAt(subVals.getIndex());
+      }
+    }
+    if (seq == null && idAv != null)
+    {
+      seq = al.findName(idAv.getValue());
+    }
+    return seq;
+  }
+
+  public AlignFrame[] getAlignFrames()
+  {
+    AlignFrame[] afs = null;
+    if (afMap != null)
+    {
+      afs = (AlignFrame[]) afMap.values().toArray();
+    }
+
+    return afs;
+  }
+
+  public List<StructureViewer> getStructureViewers()
+  {
+    List<StructureViewer> svs = null;
+    if (svMap != null)
+    {
+      for (List<StructureViewer> svList : svMap.values())
+      {
+        if (svs == null)
         {
-          return av;
+          svs = new ArrayList<>();
         }
+        svs.addAll(svList);
       }
     }
-    return null;
+    return svs;
+  }
+
+  private void addInfo(String errorMessage)
+  {
+    Console.info(errorMessage);
+    errors.add(errorMessage);
+  }
+
+  private void addWarn(String errorMessage)
+  {
+    Console.warn(errorMessage);
+    errors.add(errorMessage);
+  }
+
+  private void addError(String errorMessage)
+  {
+    addError(errorMessage, null);
+  }
+
+  private void addError(String errorMessage, Exception e)
+  {
+    Console.error(errorMessage, e);
+    errors.add(errorMessage);
+  }
+
+  private boolean checksBeforeWritingToFile(ArgValuesMap avm,
+          SubVals subVal, boolean includeBackups, String filename,
+          String adjective, Boolean isError)
+  {
+    File file = new File(filename);
+
+    boolean overwrite = ArgParser.getFromSubValArgOrPref(avm, Arg.OVERWRITE,
+            subVal, null, "OVERWRITE_OUTPUT", false);
+    boolean stdout = false;
+    boolean backups = false;
+    if (includeBackups)
+    {
+      stdout = ArgParser.STDOUTFILENAME.equals(filename);
+      // backups. Use the Arg.BACKUPS or subval "backups" setting first,
+      // otherwise if headless assume false, if not headless use the user
+      // preference with default true.
+      backups = ArgParser.getFromSubValArgOrPref(avm, Arg.BACKUPS, subVal,
+              null, Platform.isHeadless() ? null : BackupFiles.ENABLED,
+              !Platform.isHeadless());
+    }
+
+    if (file.exists() && !(overwrite || backups || stdout))
+    {
+      addWarn("Won't overwrite file '" + filename + "' without "
+              + Arg.OVERWRITE.argString()
+              + (includeBackups ? " or " + Arg.BACKUPS.argString() : "")
+              + " set");
+      return false;
+    }
+
+    boolean mkdirs = ArgParser.getFromSubValArgOrPref(avm, Arg.MKDIRS,
+            subVal, null, "MKDIRS_OUTPUT", false);
+
+    if (!FileUtils.checkParentDir(file, mkdirs))
+    {
+      addError("Directory '"
+              + FileUtils.getParentDir(file).getAbsolutePath()
+              + "' does not exist for " + adjective + " file '" + filename
+              + "'."
+              + (mkdirs ? "" : "  Try using " + Arg.MKDIRS.argString()));
+      isError = true;
+      return false;
+    }
+
+    return true;
+  }
+
+  public List<String> getErrors()
+  {
+    return errors;
+  }
+
+  public String errorsToString()
+  {
+    StringBuilder sb = new StringBuilder();
+    for (String error : errors)
+    {
+      if (sb.length() > 0)
+        sb.append("\n");
+      sb.append("- " + error);
+    }
+    return sb.toString();
   }
 }