Merge branch 'develop' into bug/JAL-4290_headless_alignment_export_with_structure_ann...
[jalview.git] / src / jalview / bin / Commands.java
index faa5e43..b6a5a25 100644 (file)
@@ -1,9 +1,28 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
 package jalview.bin;
 
 import java.awt.Color;
 import java.io.File;
 import java.io.IOException;
-import java.lang.reflect.Field;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -14,6 +33,8 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 
+import javax.swing.SwingUtilities;
+
 import jalview.analysis.AlignmentUtils;
 import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Jalview.ExitCode;
@@ -25,8 +46,6 @@ import jalview.bin.argparser.SubVals;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.annotations.AlphaFoldAnnotationRowBuilder;
-import jalview.ext.jmol.JalviewJmolBinding;
-import jalview.ext.jmol.JmolCommands;
 import jalview.gui.AlignFrame;
 import jalview.gui.AlignmentPanel;
 import jalview.gui.AppJmol;
@@ -51,7 +70,6 @@ import jalview.io.exceptions.ImageOutputException;
 import jalview.schemes.ColourSchemeI;
 import jalview.schemes.ColourSchemeProperty;
 import jalview.structure.StructureCommandI;
-import jalview.structure.StructureCommandsI;
 import jalview.structure.StructureImportSettings.TFType;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.ColorUtils;
@@ -161,8 +179,7 @@ public class Commands
 
     if (argParser.getBoolean(Arg.QUIT))
     {
-      Jalview.getInstance().exit(
-              "Exiting due to " + Arg.QUIT.argString() + " argument.",
+      Jalview.exit("Exiting due to " + Arg.QUIT.argString() + " argument.",
               ExitCode.OK);
       return true;
     }
@@ -192,17 +209,21 @@ public class Commands
 
     Boolean isError = Boolean.valueOf(false);
 
-    // set wrap scope here so it can be applied after structures are opened
+    // set wrap, showSSAnnotations, showAnnotations and hideTFrows scope here so
+    // it can be applied after structures are opened
     boolean wrap = false;
+    boolean showSSAnnotations = false;
+    boolean showAnnotations = false;
+    boolean hideTFrows = false;
+    AlignFrame af = null;
 
     if (avm.containsArg(Arg.APPEND) || avm.containsArg(Arg.OPEN))
     {
       commandArgsProvided = true;
-      long progress = -1;
+      final long progress = System.currentTimeMillis();
 
       boolean first = true;
       boolean progressBarSet = false;
-      AlignFrame af;
       // Combine the APPEND and OPEN files into one list, along with whether it
       // was APPEND or OPEN
       List<ArgValue> openAvList = new ArrayList<>();
@@ -224,10 +245,18 @@ public class Commands
           first = false;
           if (!headless && desktop != null)
           {
-            desktop.setProgressBar(
-                    MessageManager.getString(
-                            "status.processing_commandline_args"),
-                    progress = System.currentTimeMillis());
+            SwingUtilities.invokeLater(new Runnable()
+            {
+              @Override
+              public void run()
+              {
+                desktop.setProgressBar(
+                        MessageManager.getString(
+                                "status.processing_commandline_args"),
+                        progress);
+
+              }
+            });
             progressBarSet = true;
           }
         }
@@ -302,9 +331,17 @@ public class Commands
           }
 
           // colour alignment
-          String colour = avm.getFromSubValArgOrPref(av, Arg.COLOUR, sv,
-                  null, "DEFAULT_COLOUR_PROT", "");
-          this.colourAlignFrame(af, colour);
+          String colour = null;
+          if (avm.containsArg(Arg.COLOUR)
+                  || !(format == FileFormat.Jalview))
+          {
+            colour = avm.getFromSubValArgOrPref(av, Arg.COLOUR, sv, null,
+                    "DEFAULT_COLOUR_PROT", null);
+          }
+          if (colour != null)
+          {
+            this.colourAlignFrame(af, colour);
+          }
 
           // Change alignment frame title
           String title = avm.getFromSubValArgOrPref(av, Arg.TITLE, sv, null,
@@ -366,28 +403,17 @@ public class Commands
           }
 
           // Show secondary structure annotations?
-          boolean showSSAnnotations = avm.getFromSubValArgOrPref(
+          showSSAnnotations = avm.getFromSubValArgOrPref(
                   Arg.SHOWSSANNOTATIONS, av.getSubVals(), null,
                   "STRUCT_FROM_PDB", true);
-          af.setAnnotationsVisibility(showSSAnnotations, true, false);
-
           // Show sequence annotations?
-          boolean showAnnotations = avm.getFromSubValArgOrPref(
-                  Arg.SHOWANNOTATIONS, av.getSubVals(), null,
-                  "SHOW_ANNOTATIONS", true);
-          af.setAnnotationsVisibility(showAnnotations, false, true);
+          showAnnotations = avm.getFromSubValArgOrPref(Arg.SHOWANNOTATIONS,
+                  av.getSubVals(), null, "SHOW_ANNOTATIONS", true);
+          // hide the Temperature Factor row?
+          hideTFrows = (avm.getBoolean(Arg.NOTEMPFAC));
 
-          // show temperature factor annotations?
-          if (avm.getBoolean(Arg.NOTEMPFAC))
-          {
-            // do this better (annotation types?)
-            List<String> hideThese = new ArrayList<>();
-            hideThese.add("Temperature Factor");
-            hideThese.add(AlphaFoldAnnotationRowBuilder.LABEL);
-            AlignmentUtils.showOrHideSequenceAnnotations(
-                    af.getCurrentView().getAlignment(), hideThese, null,
-                    false, false);
-          }
+          // showSSAnnotations, showAnnotations, hideTFrows used after opening
+          // structure
 
           // wrap alignment? do this last for formatting reasons
           wrap = avm.getFromSubValArgOrPref(Arg.WRAP, sv, null,
@@ -412,9 +438,11 @@ public class Commands
         {
           Console.debug(
                   "Opening '" + openFile + "' in existing alignment frame");
+
           DataSourceType dst = HttpUtils.startsWithHttpOrHttps(openFile)
                   ? DataSourceType.URL
                   : DataSourceType.FILE;
+
           FileLoader fileLoader = new FileLoader(!headless);
           fileLoader.LoadFile(af.getCurrentView(), openFile, dst, null,
                   false);
@@ -443,17 +471,20 @@ public class Commands
     // open the structure (from same PDB file or given PDBfile)
     if (!avm.getBoolean(Arg.NOSTRUCTURE))
     {
-      AlignFrame af = afMap.get(id);
+      if (af == null)
+      {
+        af = afMap.get(id);
+      }
       if (avm.containsArg(Arg.STRUCTURE))
       {
         commandArgsProvided = true;
-        for (ArgValue av : avm.getArgValueList(Arg.STRUCTURE))
+        for (ArgValue structureAv : avm.getArgValueList(Arg.STRUCTURE))
         {
           argParser.setStructureFilename(null);
-          String val = av.getValue();
-          SubVals subVals = av.getSubVals();
-          int argIndex = av.getArgIndex();
-          SequenceI seq = getSpecifiedSequence(af, avm, av);
+          String val = structureAv.getValue();
+          SubVals subVals = structureAv.getSubVals();
+          int argIndex = structureAv.getArgIndex();
+          SequenceI seq = getSpecifiedSequence(af, avm, structureAv);
           if (seq == null)
           {
             // Could not find sequence from subId, let's assume the first
@@ -522,8 +553,8 @@ public class Commands
 
           // get PAEMATRIX file and label from subvals or Arg.PAEMATRIX
           String paeFilepath = avm.getFromSubValArgOrPrefWithSubstitutions(
-                  argParser, Arg.PAEMATRIX, ArgValuesMap.Position.AFTER, av,
-                  subVals, null, null, null);
+                  argParser, Arg.PAEMATRIX, ArgValuesMap.Position.AFTER,
+                  structureAv, subVals, null, null, null);
           if (paeFilepath != null)
           {
             File paeFile = new File(paeFilepath);
@@ -547,8 +578,8 @@ public class Commands
           // get TEMPFAC type from subvals or Arg.TEMPFAC in case user Adds
           // reference annotations
           String tftString = avm.getFromSubValArgOrPrefWithSubstitutions(
-                  argParser, Arg.TEMPFAC, ArgValuesMap.Position.AFTER, av,
-                  subVals, null, null, null);
+                  argParser, Arg.TEMPFAC, ArgValuesMap.Position.AFTER,
+                  structureAv, subVals, null, null, null);
           boolean notempfac = avm.getFromSubValArgOrPref(Arg.NOTEMPFAC,
                   subVals, null, "ADD_TEMPFACT_ANN", false, true);
           TFType tft = notempfac ? null : TFType.DEFAULT;
@@ -580,8 +611,8 @@ public class Commands
           }
 
           String sViewerName = avm.getFromSubValArgOrPref(
-                  Arg.STRUCTUREVIEWER, ArgValuesMap.Position.AFTER, av,
-                  subVals, null, null, "jmol");
+                  Arg.STRUCTUREVIEWER, ArgValuesMap.Position.AFTER,
+                  structureAv, subVals, null, null, "jmol");
           ViewerType viewerType = ViewerType.getFromString(sViewerName);
 
           // TODO use ssFromStructure
@@ -642,7 +673,8 @@ public class Commands
           if (avm.containsArg(Arg.STRUCTUREIMAGE))
           {
             for (ArgValue structureImageArgValue : avm
-                    .getArgValueList(Arg.STRUCTUREIMAGE))
+                    .getArgValueListFromSubValOrArg(structureAv,
+                            Arg.STRUCTUREIMAGE, subVals))
             {
               String structureImageFilename = argParser.makeSubstitutions(
                       structureImageArgValue.getValue(), id, true);
@@ -686,7 +718,8 @@ public class Commands
 
                 /////
                 // DON'T TRY TO EXPORT IF VIEWER IS UNSUPPORTED
-                if (viewerType!=ViewerType.JMOL) {
+                if (viewerType != ViewerType.JMOL)
+                {
                   addWarn("Cannot export image for structure viewer "
                           + viewerType.name() + " yet");
                   continue;
@@ -695,7 +728,7 @@ public class Commands
                 /////
                 // Apply the temporary colourscheme to the linked alignment
                 // TODO: enhance for multiple linked alignments.
-                
+
                 String imageColour = avm.getValueFromSubValOrArg(
                         structureImageArgValue, Arg.IMAGECOLOUR,
                         structureImageSubVals);
@@ -712,27 +745,12 @@ public class Commands
                 Color bgcolour = null;
                 if (bgcolourstring != null && bgcolourstring.length() > 0)
                 {
-                  try
-                  {
-                    // 
-                    // FIXME: Why not use ColorUtils.parseColourString(bgcolourstring) - this is consistent and backwards compatible
-                    //
-                    if (bgcolourstring.charAt(0) == '#')
-                    {
-                      bgcolour = Color.decode(bgcolourstring);
-                    }
-                    else
-                    {
-                      Field field = Color.class.getField(bgcolourstring);
-                      bgcolour = (Color) field.get(null);
-                    }
-                  } catch (IllegalArgumentException | NoSuchFieldException
-                          | SecurityException | IllegalAccessException nfe)
+                  bgcolour = ColorUtils.parseColourString(bgcolourstring);
+                  if (bgcolour == null)
                   {
                     Console.warn(
                             "Background colour string '" + bgcolourstring
                                     + "' not recognised -- using default");
-                    //bgcolour = Color.black;
                   }
                 }
 
@@ -740,72 +758,82 @@ public class Commands
                         .getJalviewStructureDisplay();
 
                 File sessionToRestore = null;
-                
-                List<StructureCommandI> extraCommands=new ArrayList<>();
-                
-                if (extraCommands.size() > 0 || bgcolour!=null)
+
+                List<StructureCommandI> extraCommands = new ArrayList<>();
+
+                if (extraCommands.size() > 0 || bgcolour != null)
                 {
-                  try {
+                  try
+                  {
                     sessionToRestore = sview.saveSession();
                   } catch (Throwable t)
                   {
-                    Console.warn("Unable to save temporary session file before custom structure view export operation.");
+                    Console.warn(
+                            "Unable to save temporary session file before custom structure view export operation.");
                   }
                 }
-                
+
                 ////
-                // Do temporary ops 
+                // Do temporary ops
+
+                if (bgcolour != null)
+                {
+                  sview.getBinding().setBackgroundColour(bgcolour);
+                }
+
+                sview.getBinding().executeCommands(extraCommands, false,
+                        "Executing Custom Commands");
 
-                sview.getBinding().setBackgroundColour(bgcolour);
-                
-                sview.getBinding().executeCommands(extraCommands, false, "Executing Custom Commands");
-                
                 // and export the view as an image
                 boolean success = this.checksBeforeWritingToFile(avm,
                         subVals, false, structureImageFilename,
                         "structure image", isError);
-                
+
                 if (!success)
                 {
                   continue;
                 }
-                Console.debug(
-                        "Rendering image to " + structureImageFile);
-                // 
-                // TODO - extend StructureViewer / Binding with makePDBImage so we can do this with every viewer
-                // 
-                
-                try {
+                Console.debug("Rendering image to " + structureImageFile);
+                //
+                // TODO - extend StructureViewer / Binding with makePDBImage so
+                // we can do this with every viewer
+                //
+
+                try
+                {
                   // We don't expect class cast exception
                   AppJmol jmol = (AppJmol) sview;
-                  jmol.makePDBImage(structureImageFile, imageType,
-                              renderer, userBis);
-                  Console.debug("Finished Rendering image to "
+                  jmol.makePDBImage(structureImageFile, imageType, renderer,
+                          userBis);
+                  Console.info("Exported structure image to "
                           + structureImageFile);
-    
+
                   // RESTORE SESSION AFTER EXPORT IF NEED BE
                   if (sessionToRestore != null)
                   {
-                    Console.debug("Restoring session from "
-                            + sessionToRestore);
-                    
-                    sview.getBinding().restoreSession(sessionToRestore.getAbsolutePath());
+                    Console.debug(
+                            "Restoring session from " + sessionToRestore);
+
+                    sview.getBinding().restoreSession(
+                            sessionToRestore.getAbsolutePath());
 
                   }
-                } catch (ImageOutputException ioexc)
+                } catch (ImageOutputException ioexec)
                 {
-                  addError("Unexpected error whilst exporting image to "
-                          + structureImageFile, ioexc);
+                  addError(
+                          "Unexpected error when restoring structure viewer session after custom view operations.");
                   isError = true;
                   continue;
-                }
-                finally
+                } finally
                 {
-                  try {
+                  try
+                  {
                     this.colourAlignFrame(af, originalColourScheme);
                   } catch (Exception t)
                   {
-                    addError("Unexpected error when restoring colourscheme to alignment after temporary change for export.",t);
+                    addError(
+                            "Unexpected error when restoring colourscheme to alignment after temporary change for export.",
+                            t);
                   }
                 }
               }
@@ -816,9 +844,47 @@ public class Commands
       }
     }
 
+    if (af == null)
+    {
+      af = afMap.get(id);
+    }
+    // many of jalview's format/layout methods are only thread safe on the
+    // swingworker thread.
+    // all these methods should be on the alignViewController so it can
+    // coordinate such details
+    if (headless)
+    {
+      showOrHideAnnotations(af, showSSAnnotations, showAnnotations,
+              hideTFrows);
+    }
+    else
+    {
+      try
+      {
+        AlignFrame _af = af;
+        final boolean _showSSAnnotations = showSSAnnotations;
+        final boolean _showAnnotations = showAnnotations;
+        final boolean _hideTFrows = hideTFrows;
+        SwingUtilities.invokeAndWait(() -> {
+          showOrHideAnnotations(_af, _showSSAnnotations, _showAnnotations,
+                  _hideTFrows);
+        }
+
+        );
+      } catch (Exception x)
+      {
+        Console.warn(
+                "Unexpected exception adjusting annotation row visibility.",
+                x);
+      }
+    }
+
     if (wrap)
     {
-      AlignFrame af = afMap.get(id);
+      if (af == null)
+      {
+        af = afMap.get(id);
+      }
       if (af != null)
       {
         af.setWrapFormat(wrap, true);
@@ -829,42 +895,70 @@ public class Commands
     boolean doShading = avm.getBoolean(Arg.TEMPFAC_SHADING);
     if (doShading)
     {
-      AlignFrame af = afMap.get(id);
-      for (AlignmentAnnotation aa : af.alignPanel.getAlignment()
-              .findAnnotation(PDBChain.class.getName().toString()))
-      {
-        AnnotationColourGradient acg = new AnnotationColourGradient(aa,
-                af.alignPanel.av.getGlobalColourScheme(), 0);
-        acg.setSeqAssociated(true);
-        af.changeColour(acg);
-        Console.info("Changed colour " + acg.toString());
-      }
+    AlignFrame af = afMap.get(id);
+    for (AlignmentAnnotation aa : af.alignPanel.getAlignment()
+            .findAnnotation(PDBChain.class.getName().toString()))
+    {
+      AnnotationColourGradient acg = new AnnotationColourGradient(aa,
+              af.alignPanel.av.getGlobalColourScheme(), 0);
+      acg.setSeqAssociated(true);
+      af.changeColour(acg);
+      Console.info("Changed colour " + acg.toString());
+    }
     }
     */
 
     return theseArgsWereParsed && !isError;
   }
 
+  private static void showOrHideAnnotations(AlignFrame af,
+          boolean showSSAnnotations, boolean showAnnotations,
+          boolean hideTFrows)
+  {
+    af.setAnnotationsVisibility(showSSAnnotations, true, false);
+    af.setAnnotationsVisibility(showAnnotations, false, true);
+
+    // show temperature factor annotations?
+    if (hideTFrows)
+    {
+      // do this better (annotation types?)
+      List<String> hideThese = new ArrayList<>();
+      hideThese.add("Temperature Factor");
+      hideThese.add(AlphaFoldAnnotationRowBuilder.LABEL);
+      AlignmentUtils.showOrHideSequenceAnnotations(
+              af.getCurrentView().getAlignment(), hideThese, null, false,
+              false);
+    }
+  }
+
   protected void processGroovyScript(String id)
   {
     ArgValuesMap avm = argParser.getLinkedArgs(id);
     AlignFrame af = afMap.get(id);
 
-    if (af == null)
+    if (avm != null && !avm.containsArg(Arg.GROOVY))
     {
-      addWarn("Did not have an alignment window for id=" + id);
+      // nothing to do
       return;
     }
 
+    if (af == null)
+    {
+      addWarn("Groovy script does not have an alignment window.  Proceeding with caution!");
+    }
+
     if (avm.containsArg(Arg.GROOVY))
     {
-      String groovyscript = avm.getValue(Arg.GROOVY);
-      if (groovyscript != null)
+      for (ArgValue groovyAv : avm.getArgValueList(Arg.GROOVY))
       {
-        // 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);
+        String groovyscript = groovyAv.getValue();
+        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);
+        }
       }
     }
   }
@@ -874,9 +968,16 @@ public class Commands
     ArgValuesMap avm = argParser.getLinkedArgs(id);
     AlignFrame af = afMap.get(id);
 
+    if (avm != null && !avm.containsArg(Arg.IMAGE))
+    {
+      // nothing to do
+      return true;
+    }
+
     if (af == null)
     {
-      addWarn("Did not have an alignment window for id=" + id);
+      addWarn("Do not have an alignment window to create image from (id="
+              + id + ").  Not proceeding.");
       return false;
     }
 
@@ -1003,9 +1104,16 @@ public class Commands
     ArgValuesMap avm = argParser.getLinkedArgs(id);
     AlignFrame af = afMap.get(id);
 
+    if (avm != null && !avm.containsArg(Arg.OUTPUT))
+    {
+      // nothing to do
+      return true;
+    }
+
     if (af == null)
     {
-      addWarn("Did not have an alignment window for id=" + id);
+      addWarn("Do not have an alignment window (id=" + id
+              + ").  Not proceeding.");
       return false;
     }
 
@@ -1193,8 +1301,19 @@ public class Commands
 
   private void colourAlignFrame(AlignFrame af, ColourSchemeI cs)
   {
-    // Note that cs == null removes colour scheme from af
-    af.changeColour(cs);
+    try {
+    SwingUtilities.invokeAndWait(new Runnable()
+    {
+      @Override
+      public void run()
+      {
+        // Note that cs == null removes colour scheme from af
+        af.changeColour(cs);
+      }
+    }); } catch (Exception x) {
+      Console.trace("Interrupted whilst waiting for colorAlignFrame action",x);
+      
+    }
   }
 
   private ColourSchemeI getColourScheme(AlignFrame af)