Merge branch 'develop' into features/r2_11_2/JAL-3829_3dbeacons
authorJim Procter <j.procter@dundee.ac.uk>
Tue, 14 Sep 2021 11:58:51 +0000 (12:58 +0100)
committerJim Procter <j.procter@dundee.ac.uk>
Tue, 14 Sep 2021 11:58:51 +0000 (12:58 +0100)
src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java
src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java
src/jalview/bin/Cache.java
src/jalview/ext/rbvi/chimera/ChimeraXCommands.java
src/jalview/gui/Preferences.java
src/jalview/jbgui/GPreferences.java

index d7e7937..19d6a8b 100644 (file)
@@ -38,6 +38,9 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -363,7 +366,7 @@ public class ChimeraManager
    * Select something in Chimera
    * 
    * @param command
-   *          the selection command to pass to Chimera
+   *                  the selection command to pass to Chimera
    */
   public void select(String command)
   {
@@ -509,8 +512,8 @@ public class ChimeraManager
 
   /**
    * Return the list of depiction presets available from within Chimera. Chimera
-   * will return the list as a series of lines with the format: Preset type
-   * number "description"
+   * will return the list as a series of lines with the format: Preset type number
+   * "description"
    * 
    * @return list of presets
    */
@@ -551,9 +554,9 @@ public class ChimeraManager
   }
 
   /**
-   * Launch Chimera, unless an instance linked to this object is already
-   * running. Returns true if chimera is successfully launched, or already
-   * running, else false.
+   * Launch Chimera, unless an instance linked to this object is already running.
+   * Returns true if chimera is successfully launched, or already running, else
+   * false.
    * 
    * @param chimeraPaths
    * @return
@@ -696,7 +699,7 @@ public class ChimeraManager
    * Determine the color that Chimera is using for this model.
    * 
    * @param model
-   *          the ChimeraModel we want to get the Color for
+   *                the ChimeraModel we want to get the Color for
    * @return the default model Color for this model in Chimera
    */
   public Color getModelColor(ChimeraModel model)
@@ -713,11 +716,11 @@ public class ChimeraManager
   /**
    * 
    * Get information about the residues associated with a model. This uses the
-   * Chimera listr command. We don't return the resulting residues, but we add
-   * the residues to the model.
+   * Chimera listr command. We don't return the resulting residues, but we add the
+   * residues to the model.
    * 
    * @param model
-   *          the ChimeraModel to get residue information for
+   *                the ChimeraModel to get residue information for
    * 
    */
   public void addResidues(ChimeraModel model)
@@ -809,10 +812,10 @@ public class ChimeraManager
    * Send a command to Chimera.
    * 
    * @param command
-   *          Command string to be send.
+   *                  Command string to be send.
    * @param reply
-   *          Flag indicating whether the method should return the reply from
-   *          Chimera or not.
+   *                  Flag indicating whether the method should return the reply
+   *                  from Chimera or not.
    * @return List of Strings corresponding to the lines in the Chimera reply or
    *         <code>null</code>.
    */
@@ -833,7 +836,7 @@ public class ChimeraManager
      */
     int waited = 0;
     int pause = 25;
-    while (busy  && waited < 1001)
+    while (busy && waited < 1001)
     {
       try
       {
@@ -876,8 +879,15 @@ public class ChimeraManager
     String method = getHttpRequestMethod();
     if ("GET".equals(method))
     {
-      command = command.replace(" ", "+").replace("#", "%23")
-              .replace("|", "%7C").replace(";", "%3B");
+      try
+      {
+        command = URLEncoder.encode(command, StandardCharsets.UTF_8.name());
+      } catch (UnsupportedEncodingException e)
+      {
+        command = command.replace(" ", "+").replace("#", "%23")
+                .replace("|", "%7C").replace(";", "%3B")
+                .replace(":", "%3A");
+      }
     }
     commands.add(new BasicNameValuePair("command", command));
 
index 5cf8a73..9078d12 100644 (file)
@@ -69,13 +69,13 @@ public class StructureManager
    * 0.93 is ChimeraX latest, 1.0 expected soon
    */
   private static String[] CHIMERA_VERSIONS = new String[] { "1.16.2",
-      "1.16.1", "1.16",
-      "1.15.2", "1.15.1", "1.15", "1.14.2", "1.14.1", "1.14",
-      "1.13.1", "1.13", "1.12.2", "1.12.1", "1.12", "1.11.2",
+      "1.16.1", "1.16", "1.15.2", "1.15.1", "1.15", "1.14.2", "1.14.1",
+      "1.14", "1.13.1", "1.13", "1.12.2", "1.12.1", "1.12", "1.11.2",
       "1.11.2", "1.11.1", "1.11" };
 
-  private static String[] CHIMERAX_VERSIONS = new String[] { "1.0", "0.93",
-      "0.92", "0.91", "0.9" };
+  // Missing 1.1 as this has known bug see JAL-2422
+  private static String[] CHIMERAX_VERSIONS = new String[] { "1.2.5", "1.0",
+      "0.93", "0.92", "0.91", "0.9" };
 
   static final String[] defaultStructureKeys = { "Structure", "pdb",
       "pdbFileName", "PDB ID", "structure", "biopax.xref.PDB", "pdb_ids",
@@ -561,8 +561,8 @@ public class StructureManager
 
   /**
    * This is called by the selectionListener to let us know that the user has
-   * changed their selection in Chimera. We need to go back to Chimera to find
-   * out what is currently selected and update our list.
+   * changed their selection in Chimera. We need to go back to Chimera to find out
+   * what is currently selected and update our list.
    */
   public void chimeraSelectionChanged()
   {
@@ -677,11 +677,11 @@ public class StructureManager
   }
 
   /**
-   * Add a selection to the selection list. This is called primarily by the
-   * Model Navigator Dialog to keep the selections in sync
+   * Add a selection to the selection list. This is called primarily by the Model
+   * Navigator Dialog to keep the selections in sync
    * 
    * @param selectionToAdd
-   *          the selection to add to our list
+   *                         the selection to add to our list
    */
   public void addChimSelection(ChimeraStructuralObject selectionToAdd)
   {
@@ -698,7 +698,7 @@ public class StructureManager
    * Model Navigator Dialog to keep the selections in sync
    * 
    * @param selectionToRemove
-   *          the selection to remove from our list
+   *                            the selection to remove from our list
    */
   public void removeChimSelection(ChimeraStructuralObject selectionToRemove)
   {
@@ -936,16 +936,10 @@ public class StructureManager
     // }
     // }
 
-    /*
-     * Jalview addition: check if path set in user preferences
-     */
+    String os = System.getProperty("os.name");
     String userPath = Cache
             .getDefault(isChimeraX ? Preferences.CHIMERAX_PATH
                     : Preferences.CHIMERA_PATH, null);
-    if (userPath != null)
-    {
-      pathList.add(userPath);
-    }
 
     /*
      * paths are based on getChimeraPaths() in
@@ -957,15 +951,61 @@ public class StructureManager
     String chimera = isChimeraX ? "ChimeraX" : "Chimera";
     String chimeraExe = isChimeraX ? "ChimeraX" : "chimera";
 
+    /*
+     * Jalview addition: check if path set in user preferences
+     */
+    if (userPath != null)
+    {
+      // in macos, deal with the user selecting the .app folder
+      boolean adjusted = false;
+      if (os.startsWith("Mac") && userPath.endsWith((".app")))
+      {
+        String possiblePath = String.format("%s/Contents/MacOS/%s",
+                userPath, chimeraExe);
+        if (new File(possiblePath).exists())
+        {
+          pathList.add(possiblePath);
+          adjusted = true;
+        }
+      }
+      if (!adjusted)
+      {
+        pathList.add(userPath);
+      }
+    }
+
     // Add default installation paths
-    String os = System.getProperty("os.name");
     if (os.startsWith("Linux"))
     {
-      // todo should this be /chimeraX/ for ChimeraX? not in structureVizX code
-      pathList.add("/usr/local/chimera/bin/" + chimeraExe);
-      pathList.add("/usr/local/bin/" + chimeraExe);
-      pathList.add("/usr/bin/" + chimeraExe);
-      pathList.add(System.getProperty("user.home") + "/opt/bin/" + chimeraExe);
+      // ChimeraX .deb and .rpm packages put symbolic link from /usr/bin/chimerax
+      pathList.add(String.format("/usr/bin/%s", chimeraExe.toLowerCase()));
+      pathList.add(String.format("/usr/bin/%s", chimeraExe));
+
+      pathList.add(
+              String.format("/usr/local/bin/%s", chimeraExe.toLowerCase()));
+      pathList.add(String.format("/usr/local/bin/%s", chimeraExe));
+
+      // these paths also used by .deb and .rpm
+      pathList.add(String.format("/usr/lib/ucsf-%s/bin/%s",
+              chimera.toLowerCase(), chimeraExe));
+      pathList.add(String.format("/usr/libexec/UCSF-%s/bin/%s", chimera,
+              chimeraExe));
+
+      pathList.add(String.format("/usr/local/chimera/bin/%s", chimeraExe));
+
+      // user home paths
+      pathList.add(String.format("%s/bin/%s",
+              System.getProperty("user.home"), chimeraExe.toLowerCase()));
+      pathList.add(String.format("%s/bin/%s",
+              System.getProperty("user.home"), chimeraExe));
+      pathList.add(String.format("%s/opt/bin/%s",
+              System.getProperty("user.home"), chimeraExe.toLowerCase()));
+      pathList.add(String.format("%s/opt/bin/%s",
+              System.getProperty("user.home"), chimeraExe));
+      pathList.add(String.format("%s/local/bin/%s",
+              System.getProperty("user.home"), chimeraExe.toLowerCase()));
+      pathList.add(String.format("%s/local/bin/%s",
+              System.getProperty("user.home"), chimeraExe));
     }
     else if (os.startsWith("Windows"))
     {
@@ -983,10 +1023,24 @@ public class StructureManager
           pathList.add(path);
           pathList.add(path + ".exe");
         }
+        // try without a version number too
+        String path = String.format("%s\\%s\\bin\\%s", root, chimera,
+                chimeraExe);
+        pathList.add(path);
+        pathList.add(path + ".exe");
       }
     }
     else if (os.startsWith("Mac"))
     {
+      // check for installations with version numbers first
+      String[] candidates = isChimeraX ? CHIMERAX_VERSIONS
+              : CHIMERA_VERSIONS;
+      for (String version : candidates)
+      {
+        pathList.add(
+                String.format("/Applications/%s-%s.app/Contents/MacOS/%s",
+                        chimera, version, chimeraExe));
+      }
       pathList.add(String.format("/Applications/%s.app/Contents/MacOS/%s",
               chimera, chimeraExe));
     }
index 353f449..216b498 100755 (executable)
@@ -370,8 +370,8 @@ public class Cache
   }
 
   /**
-   * Loads properties from the given properties file. Any existing properties
-   * are first cleared.
+   * Loads properties from the given properties file. Any existing properties are
+   * first cleared.
    */
   public static void loadProperties(String propsFile)
   {
@@ -710,11 +710,10 @@ public class Cache
   }
 
   /**
-   * Gets Jalview application property of given key. Returns null if key not
-   * found
+   * Gets Jalview application property of given key. Returns null if key not found
    * 
    * @param key
-   *          Name of property
+   *              Name of property
    * 
    * @return Property value
    */
@@ -730,8 +729,8 @@ public class Cache
   }
 
   /**
-   * These methods are used when checking if the saved preference is different
-   * to the default setting
+   * These methods are used when checking if the saved preference is different to
+   * the default setting
    */
 
   public static boolean getDefault(String property, boolean def)
@@ -764,8 +763,8 @@ public class Cache
   }
 
   /**
-   * Answers the value of the given property, or the supplied default value if
-   * the property is not set
+   * Answers the value of the given property, or the supplied default value if the
+   * property is not set
    */
   public static String getDefault(String property, String def)
   {
@@ -777,9 +776,9 @@ public class Cache
    * Stores property in the file "HOME_DIR/.jalview_properties"
    * 
    * @param key
-   *          Name of object
+   *              Name of object
    * @param obj
-   *          String value of property
+   *              String value of property
    * 
    * @return previous value of property (or null)
    */
@@ -1167,7 +1166,7 @@ public class Cache
    * Loads in user colour schemes from files.
    * 
    * @param files
-   *          a '|'-delimited list of file paths
+   *                a '|'-delimited list of file paths
    */
   public static void initUserColourSchemes(String files)
   {
@@ -1476,8 +1475,8 @@ public class Cache
                   // open Preferences -> Connections
                   String message = MessageManager
                           .getString("label.proxy_password_required");
-                  Preferences.openPreferences(Preferences.CONNECTIONS_TAB,
-                          message);
+                  Preferences.openPreferences(
+                          Preferences.TabRef.CONNECTIONS_TAB, message);
                   Preferences.getInstance()
                           .proxyAuthPasswordCheckHighlight(true, true);
                 }
index ad04fc9..4e45ac8 100644 (file)
@@ -35,15 +35,19 @@ import jalview.structure.StructureCommandI;
 public class ChimeraXCommands extends ChimeraCommands
 {
   // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/info.html#resattr
-  private static final StructureCommand LIST_RESIDUE_ATTRIBUTES = new StructureCommand("info resattr");
+  private static final StructureCommand LIST_RESIDUE_ATTRIBUTES = new StructureCommand(
+          "info resattr");
 
   // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/exit.html
-  private static final StructureCommand CLOSE_CHIMERAX = new StructureCommand("exit");
+  private static final StructureCommand CLOSE_CHIMERAX = new StructureCommand(
+          "exit");
 
   // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/info.html#notify
-  private static final StructureCommand STOP_NOTIFY_SELECTION = new StructureCommand("info notify stop selection jalview");
+  private static final StructureCommand STOP_NOTIFY_SELECTION = new StructureCommand(
+          "info notify stop selection jalview");
 
-  private static final StructureCommand STOP_NOTIFY_MODELS = new StructureCommand("info notify stop models jalview");
+  private static final StructureCommand STOP_NOTIFY_MODELS = new StructureCommand(
+          "info notify stop models jalview");
 
   // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/info.html#selection
   private static final StructureCommand GET_SELECTION = new StructureCommand(
@@ -98,8 +102,8 @@ public class ChimeraXCommands extends ChimeraCommands
   }
 
   /**
-   * Returns a viewer command to set the given residue attribute value on
-   * residues specified by the AtomSpecModel, for example
+   * Returns a viewer command to set the given residue attribute value on residues
+   * specified by the AtomSpecModel, for example
    * 
    * <pre>
    * setattr #0/A:3-9,14-20,39-43 res jv_strand 'strand' create true
@@ -141,6 +145,10 @@ public class ChimeraXCommands extends ChimeraCommands
    * Returns the range(s) formatted as a ChimeraX atomspec, for example
    * <p>
    * #1/A:2-20,30-40/B:10-20|#2/A:12-30
+   * <p>
+   * Note there is no need to explicitly exclude ALTLOC atoms when
+   * {@code alphaOnly == true}, as this is the default behaviour of ChimeraX (a
+   * change from Chimera)
    * 
    * @return
    */
@@ -162,7 +170,6 @@ public class ChimeraXCommands extends ChimeraCommands
         // TODO @P if RNA - add nucleotide flag to AtomSpecModel?
         sb.append("@CA");
       }
-      // todo: is there ChimeraX syntax to exclude altlocs?
     }
     return sb.toString();
   }
@@ -249,8 +256,12 @@ public class ChimeraXCommands extends ChimeraCommands
   public List<StructureCommandI> startNotifications(String uri)
   {
     List<StructureCommandI> cmds = new ArrayList<>();
-    cmds.add(new StructureCommand("info notify start models jalview prefix ModelChanged url " + uri));
-    cmds.add(new StructureCommand("info notify start selection jalview prefix SelectionChanged url " + uri));
+    cmds.add(new StructureCommand(
+            "info notify start models jalview prefix ModelChanged url "
+                    + uri));
+    cmds.add(new StructureCommand(
+            "info notify start selection jalview prefix SelectionChanged url "
+                    + uri));
     return cmds;
   }
 
index 6972657..834d779 100755 (executable)
@@ -31,6 +31,7 @@ import java.awt.event.MouseEvent;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
 
 import javax.help.HelpSetException;
 import javax.swing.JComboBox;
@@ -145,10 +146,10 @@ public class Preferences extends GPreferences
 
   /**
    * Holds name and link separated with | character. Sequence IDS and Sequences
-   * must be $SEQUENCEIDS$ or $SEQUENCEIDS=/.possible | chars ./=$ and
-   * $SEQUENCES$ or $SEQUENCES=/.possible | chars ./=$ and separation character
-   * for first and second token specified after a pipe character at end |,|.
-   * (TODO: proper escape for using | to separate ids or sequences
+   * must be $SEQUENCEIDS$ or $SEQUENCEIDS=/.possible | chars ./=$ and $SEQUENCES$
+   * or $SEQUENCES=/.possible | chars ./=$ and separation character for first and
+   * second token specified after a pipe character at end |,|. (TODO: proper
+   * escape for using | to separate ids or sequences
    */
 
   public static List<String> groupURLLinks;
@@ -219,19 +220,27 @@ public class Preferences extends GPreferences
 
   public static void openPreferences()
   {
-    openPreferences(0, null);
+    openPreferences(null, null);
   }
 
-  public static void openPreferences(int selectTab, String message)
+  public static void openPreferences(TabRef selectTab, String message)
   {
     Preferences p = getInstance();
-    p.selectTab(selectTab);
-    p.setMessage(message);
+    if (selectTab != null)
+      p.selectTab(selectTab, message);
     p.frame.show();
     p.frame.moveToFront();
     p.frame.grabFocus();
   }
 
+  public void selectTab(TabRef selectTab, String message)
+  {
+    this.selectTab(selectTab);
+    if (message != null)
+      this.setMessage(message);
+    this.frame.show();
+  }
+
   /**
    * Creates a new Preferences object.
    */
@@ -1009,8 +1018,8 @@ public class Preferences extends GPreferences
   }
 
   /**
-   * Do any necessary validation before saving settings. Return focus to the
-   * first tab which fails validation.
+   * Do any necessary validation before saving settings. Return focus to the first
+   * tab which fails validation.
    * 
    * @return
    */
@@ -1064,7 +1073,7 @@ public class Preferences extends GPreferences
    * DOCUMENT ME!
    * 
    * @param e
-   *          DOCUMENT ME!
+   *            DOCUMENT ME!
    */
   @Override
   public void cancel_actionPerformed(ActionEvent e)
@@ -1086,7 +1095,7 @@ public class Preferences extends GPreferences
    * DOCUMENT ME!
    * 
    * @param e
-   *          DOCUMENT ME!
+   *            DOCUMENT ME!
    */
   @Override
   public void annotations_actionPerformed(ActionEvent e)
@@ -1349,8 +1358,8 @@ public class Preferences extends GPreferences
   }
 
   /**
-   * Returns true if structure viewer path is to a valid executable, else shows
-   * an error dialog. Does nothing if the path is empty, as is the case for Jmol
+   * Returns true if structure viewer path is to a valid executable, else shows an
+   * error dialog. Does nothing if the path is empty, as is the case for Jmol
    * (built in to Jalview) or when Jalview is left to try default paths.
    */
   private boolean validateViewerPath()
@@ -1371,8 +1380,8 @@ public class Preferences extends GPreferences
   }
 
   /**
-   * If Chimera or ChimeraX or Pymol is selected, check it can be found on
-   * default or user-specified path, if not show a warning/help dialog
+   * If Chimera or ChimeraX or Pymol is selected, check it can be found on default
+   * or user-specified path, if not show a warning/help dialog
    */
   @Override
   protected void structureViewer_actionPerformed(String selectedItem)
@@ -1439,8 +1448,10 @@ public class Preferences extends GPreferences
                       MessageManager.getString("label.viewer_missing")),
               "", JvOptionPane.YES_NO_OPTION, JvOptionPane.WARNING_MESSAGE,
               null, options, options[0]);
+
       if (showHelp == JvOptionPane.NO_OPTION)
       {
+        this.selectTab(Preferences.TabRef.STRUCTURE_TAB, null);
         try
         {
           Help.showHelpWindow(HelpId.StructureViewer);
@@ -1449,6 +1460,24 @@ public class Preferences extends GPreferences
           e.printStackTrace();
         }
       }
+      else if (showHelp == JvOptionPane.OK_OPTION)
+      {
+        this.selectTab(Preferences.TabRef.STRUCTURE_TAB, null);
+        CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
+          try
+          {
+            for (int i = 0; i < 3; i++)
+            {
+              structureViewerPath.setBackground(Color.PINK);
+              Thread.sleep(500);
+              structureViewerPath.setBackground(Color.WHITE);
+              Thread.sleep(500);
+            }
+          } catch (InterruptedException e)
+          {
+          }
+        });
+      }
     }
   }
 
index 1a4a44b..e6efed6 100755 (executable)
@@ -510,9 +510,12 @@ public class GPreferences extends JPanel
     }
   }
 
-  public final static int CONNECTIONS_TAB = 5;
+  public static enum TabRef
+  {
+    CONNECTIONS_TAB, STRUCTURE_TAB
+  };
 
-  public void selectTab(int selectTab)
+  public void selectTab(TabRef selectTab)
   {
     // select a given tab - currently only for Connections
     switch (selectTab)
@@ -520,6 +523,9 @@ public class GPreferences extends JPanel
     case CONNECTIONS_TAB:
       tabbedPane.setSelectedComponent(connectTab);
       break;
+    case STRUCTURE_TAB:
+      tabbedPane.setSelectedComponent(structureTab);
+      break;
     default:
     }
   }
@@ -1789,8 +1795,8 @@ public class GPreferences extends JPanel
   }
 
   /**
-   * Show a dialog for the user to choose a file. Returns the chosen path, or
-   * null on Cancel.
+   * Show a dialog for the user to choose a file. Returns the chosen path, or null
+   * on Cancel.
    * 
    * @return
    */
@@ -3166,7 +3172,7 @@ public class GPreferences extends JPanel
    * DOCUMENT ME!
    * 
    * @param e
-   *          DOCUMENT ME!
+   *            DOCUMENT ME!
    */
   public void ok_actionPerformed(ActionEvent e)
   {
@@ -3176,7 +3182,7 @@ public class GPreferences extends JPanel
    * DOCUMENT ME!
    * 
    * @param e
-   *          DOCUMENT ME!
+   *            DOCUMENT ME!
    */
   public void cancel_actionPerformed(ActionEvent e)
   {
@@ -3186,7 +3192,7 @@ public class GPreferences extends JPanel
    * DOCUMENT ME!
    * 
    * @param e
-   *          DOCUMENT ME!
+   *            DOCUMENT ME!
    */
   public void annotations_actionPerformed(ActionEvent e)
   {