Merge branch 'develop' into feature/JAL-2422ChimeraX
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 9 Mar 2020 15:05:32 +0000 (15:05 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 9 Mar 2020 15:05:32 +0000 (15:05 +0000)
49 files changed:
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java
src/ext/edu/ucsf/rbvi/strucviz2/StructureManager.java
src/jalview/api/structures/JalviewStructureDisplayI.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/AppletJmol.java
src/jalview/appletgui/AppletJmolBinding.java
src/jalview/appletgui/ExtJmol.java
src/jalview/appletgui/UserDefinedColours.java
src/jalview/ext/jmol/JalviewJmolBinding.java
src/jalview/ext/jmol/JmolCommands.java
src/jalview/ext/rbvi/chimera/AtomSpecModel.java [deleted file]
src/jalview/ext/rbvi/chimera/ChimeraCommands.java
src/jalview/ext/rbvi/chimera/ChimeraListener.java
src/jalview/ext/rbvi/chimera/ChimeraXCommands.java [new file with mode: 0644]
src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AppJmol.java
src/jalview/gui/AppJmolBinding.java
src/jalview/gui/ChimeraViewFrame.java
src/jalview/gui/ChimeraXViewFrame.java [new file with mode: 0644]
src/jalview/gui/JalviewChimeraBindingModel.java
src/jalview/gui/JalviewChimeraXBindingModel.java [new file with mode: 0644]
src/jalview/gui/Preferences.java
src/jalview/gui/StructureViewer.java
src/jalview/gui/StructureViewerBase.java
src/jalview/gui/ViewSelectionMenu.java
src/jalview/javascript/MouseOverStructureListener.java
src/jalview/jbgui/GPreferences.java
src/jalview/jbgui/GStructureViewer.java
src/jalview/project/Jalview2XML.java
src/jalview/structure/AtomSpec.java
src/jalview/structure/AtomSpecModel.java [new file with mode: 0644]
src/jalview/structure/StructureCommandsBase.java [new file with mode: 0644]
src/jalview/structure/StructureCommandsFactory.java [new file with mode: 0644]
src/jalview/structure/StructureCommandsI.java [new file with mode: 0644]
src/jalview/structures/models/AAStructureBindingModel.java
src/jalview/ws/HttpClientUtils.java
test/jalview/ext/jmol/JmolCommandsTest.java
test/jalview/ext/rbvi/chimera/AtomSpecModelTest.java [deleted file]
test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java
test/jalview/ext/rbvi/chimera/ChimeraConnect.java
test/jalview/ext/rbvi/chimera/ChimeraXCommandsTest.java [new file with mode: 0644]
test/jalview/ext/rbvi/chimera/JalviewChimeraView.java
test/jalview/structure/AtomSpecModelTest.java [new file with mode: 0644]
test/jalview/structure/AtomSpecTest.java
test/jalview/structure/StructureSelectionManagerTest.java
test/jalview/structures/models/AAStructureBindingModelTest.java

index a4b24ed..51500c6 100644 (file)
@@ -268,7 +268,7 @@ label.autoadd_secstr = Add secondary structure annotation to alignment
 label.autoadd_temp = Add Temperature Factor annotation to alignment
 label.structure_viewer = Default structure viewer
 label.double_click_to_browse = Double-click to browse for file
-label.chimera_path = Path to Chimera program
+label.chimera_path = Path to {0} program
 label.chimera_path_tip = Jalview will first try any path entered here, else standard installation locations.<br>Double-click to browse for file.
 label.invalid_chimera_path = Chimera path not found or not executable
 label.chimera_missing = Chimera structure viewer not found.<br/>Please enter the path to Chimera (if installed),<br/>or download and install UCSF Chimera.
@@ -1129,7 +1129,7 @@ status.fetching_db_refs = Fetching db refs
 status.loading_cached_pdb_entries = Loading Cached PDB Entries
 status.searching_for_pdb_structures = Searching for PDB Structures
 status.opening_file_for = opening file for
-status.colouring_chimera = Colouring Chimera
+status.colouring_structures = Colouring structures
 label.font_doesnt_have_letters_defined = Font doesn't have letters defined\nso cannot be used\nwith alignment data
 label.font_too_small = Font size is too small
 label.error_loading_file_params = Error loading file {0}
index 4e0fb1f..199a65d 100644 (file)
@@ -1198,12 +1198,12 @@ label.confirm_close_chimera=Cerrar
 tooltip.rnalifold_calculations=Se calcularán predicciones de estructura secondaria de RNA para el alineaminento, y se actualizarán si se efectuan cambios
 tooltip.rnalifold_settings=Modificar la configuración de la predicción RNAAlifold. Úselo para ocultar o mostrar resultados del cálculo de RNA, o cambiar parámetros de el plegado de RNA.
 label.show_selected_annotations=Mostrar anotaciones seleccionadas
-status.colouring_chimera=Coloreando Chimera
+status.colouring_structures=Coloreando estructuras
 label.configure_displayed_columns=Configurar Columnas Mostradas
 label.aacon_calculations=cálculos AACon
 label.pdb_web-service_error=Error de servicio web PDB
 exception.unable_to_detect_internet_connection=Jalview no puede detectar una conexión a Internet
-label.chimera_path=Ruta de acceso a Chimera
+label.chimera_path=Ruta de acceso a {0}
 warn.delete_all=<html>Borrar todas las secuencias cerrará la ventana del alineamiento.<br>Confirmar o Cancelar.
 label.select_all=Seleccionar Todos
 label.alpha_helix=Hélice Alfa
index a910a5a..6bb3b71 100644 (file)
@@ -254,7 +254,7 @@ public class ChimeraManager
     for (ChimeraModel chimeraModel : modelList)
     {
       // get model color
-      Color modelColor = getModelColor(chimeraModel);
+      Color modelColor = isChimeraX() ? null : getModelColor(chimeraModel);
       if (modelColor != null)
       {
         chimeraModel.setModelColor(modelColor);
@@ -265,7 +265,7 @@ public class ChimeraManager
       // chimeraSend("repr stick "+newModel.toSpec());
 
       // Create the information we need for the navigator
-      if (type != ModelType.SMILES)
+      if (type != ModelType.SMILES && !isChimeraX())
       {
         addResidues(chimeraModel);
       }
@@ -334,7 +334,11 @@ public class ChimeraManager
 
   public void stopListening()
   {
-    sendChimeraCommand("listen stop models ; listen stop selection ", false);
+    // TODO send this command when viewer connection is closed in Jalview
+    String command = isChimeraX
+            ? "info notify stop models jalview; info notify stop selection jalview"
+            : "listen stop models ; listen stop selection ";
+    sendChimeraCommand(command, false);
   }
 
   /**
@@ -344,9 +348,23 @@ public class ChimeraManager
    */
   public void startListening(String uri)
   {
-    sendChimeraCommand("listen start models url " + uri
-            + ";listen start select prefix SelectionChanged url " + uri,
-            false);
+    /*
+     * listen for model changes
+     */
+    String command = isChimeraX
+            ? ("info notify start models prefix ModelChanged jalview url "
+                    + uri)
+            : ("listen start models url " + uri);
+    sendChimeraCommand(command, false);
+
+    /*
+     * listen for selection changes
+     */
+    command = isChimeraX
+            ? ("info notify start selection jalview prefix SelectionChanged url "
+                    + uri)
+            : ("listen start select prefix SelectionChanged url " + uri);
+    sendChimeraCommand(command, false);
   }
 
   /**
@@ -420,19 +438,34 @@ public class ChimeraManager
   public List<String> getSelectedResidueSpecs()
   {
     List<String> selectedResidues = new ArrayList<>();
-    List<String> chimeraReply = sendChimeraCommand(
-            "list selection level residue", true);
+
+    /*
+     * skip for now if ChimeraX - request times out
+     */
+    if (isChimeraX)
+    {
+      return selectedResidues;
+    }
+
+    // in fact 'listinfo' (undocumented) works in ChimeraX
+    String command = (isChimeraX
+            ? "info"
+            : "list") + " selection level residue";
+    List<String> chimeraReply = sendChimeraCommand(command, true);
     if (chimeraReply != null)
     {
       /*
-       * expect 0, 1 or more lines of the format
+       * expect 0, 1 or more lines of the format either
+       * Chimera:
        * residue id #0:43.A type GLY
-       * where we are only interested in the atomspec #0.43.A
+       * ChimeraX:
+       * residue id /A:89 name THR index 88
+       * We are only interested in the atomspec (third token of the reply)
        */
       for (String inputLine : chimeraReply)
       {
         String[] inputLineParts = inputLine.split("\\s+");
-        if (inputLineParts.length == 5)
+        if (inputLineParts.length >= 5)
         {
           selectedResidues.add(inputLineParts[2]);
         }
@@ -473,14 +506,21 @@ public class ChimeraManager
   public List<ChimeraModel> getModelList()
   {
     List<ChimeraModel> modelList = new ArrayList<>();
-    List<String> list = sendChimeraCommand("list models type molecule",
-            true);
+    String command = "list models type "
+            + (isChimeraX ? "AtomicStructure" : "molecule");
+    List<String> list = sendChimeraCommand(command, true);
     if (list != null)
     {
       for (String modelLine : list)
       {
-        ChimeraModel chimeraModel = new ChimeraModel(modelLine);
-        modelList.add(chimeraModel);
+        try
+        {
+          ChimeraModel chimeraModel = new ChimeraModel(modelLine);
+          modelList.add(chimeraModel);
+        } catch (NullPointerException e)
+        {
+          // hack for now
+        }
       }
     }
     return modelList;
@@ -555,6 +595,7 @@ public class ChimeraManager
       {
         // ensure symbolic links are resolved
         chimeraPath = Paths.get(chimeraPath).toRealPath().toString();
+        isChimeraX = chimeraPath.toLowerCase().contains("chimerax");
         File path = new File(chimeraPath);
         // uncomment the next line to simulate Chimera not installed
         // path = new File(chimeraPath + "x");
@@ -567,8 +608,16 @@ public class ChimeraManager
         args.add(chimeraPath);
         // shows Chimera output window but suppresses REST responses:
         // args.add("--debug");
-        args.add("--start");
-        args.add("RESTServer");
+        if (isChimeraX())
+        {
+          args.add("--cmd");
+          args.add("remote rest start");
+        }
+        else
+        {
+          args.add("--start");
+          args.add("RESTServer");
+        }
         ProcessBuilder pb = new ProcessBuilder(args);
         chimera = pb.start();
         error = "";
@@ -616,15 +665,23 @@ public class ChimeraManager
       {
         responses.append("\n" + response);
         // expect: REST server on host 127.0.0.1 port port_number
+        // ChimeraX is the same except "REST server started on host..."
         if (response.startsWith("REST server"))
         {
           String[] tokens = response.split(" ");
-          if (tokens.length == 7 && "port".equals(tokens[5]))
+          for (int i = 0; i < tokens.length - 1; i++)
           {
-            port = Integer.parseInt(tokens[6]);
-            break;
+            if ("port".equals(tokens[i]))
+            {
+              port = Integer.parseInt(tokens[i + 1]);
+              break;
+            }
           }
         }
+        if (port > 0)
+        {
+          break; // hack for hanging readLine()
+        }
         response = lineReader.readLine();
       }
     } catch (Exception e)
@@ -703,7 +760,8 @@ public class ChimeraManager
   public List<String> getAttrList()
   {
     List<String> attributes = new ArrayList<>();
-    final List<String> reply = sendChimeraCommand("list resattr", true);
+    String command = (isChimeraX ? "info " : "list ") + "resattr";
+    final List<String> reply = sendChimeraCommand(command, true);
     if (reply != null)
     {
       for (String inputLine : reply)
@@ -762,6 +820,8 @@ public class ChimeraManager
 
   private volatile boolean busy = false;
 
+  private boolean isChimeraX;
+
   /**
    * Send a command to Chimera.
    * 
@@ -775,7 +835,7 @@ public class ChimeraManager
    */
   public List<String> sendChimeraCommand(String command, boolean reply)
   {
-   // System.out.println("chimeradebug>> " + command);
+    System.out.println("chimeradebug>> " + command);
     if (!isChimeraLaunched() || command == null
             || "".equals(command.trim()))
     {
@@ -822,14 +882,23 @@ public class ChimeraManager
   {
     String restUrl = "http://127.0.0.1:" + this.chimeraRestPort + "/run";
     List<NameValuePair> commands = new ArrayList<>(1);
+    String method = isChimeraX() ? "GET" : "POST";
+    if ("GET".equals(method))
+    {
+      command = command.replace(" ", "+").replace("#", "%23")
+              .replace("|", "%7C").replace(";", "%3B");
+    }
     commands.add(new BasicNameValuePair("command", command));
 
     List<String> reply = new ArrayList<>();
     BufferedReader response = null;
     try
     {
-      response = HttpClientUtils.doHttpUrlPost(restUrl, commands, CONNECTION_TIMEOUT_MS,
-              REST_REPLY_TIMEOUT_MS);
+      response = "GET".equals(method)
+              ? HttpClientUtils.doHttpGet(restUrl, commands,
+                      CONNECTION_TIMEOUT_MS, REST_REPLY_TIMEOUT_MS)
+              : HttpClientUtils.doHttpUrlPost(restUrl, commands,
+                      CONNECTION_TIMEOUT_MS, REST_REPLY_TIMEOUT_MS);
       String line = "";
       while ((line = response.readLine()) != null)
       {
@@ -901,4 +970,14 @@ public class ChimeraManager
   {
     return chimera;
   }
+
+  public boolean isChimeraX()
+  {
+    return isChimeraX;
+  }
+
+  public void setChimeraX(boolean b)
+  {
+    isChimeraX = b;
+  }
 }
index 22c9098..b7ea4ad 100644 (file)
@@ -896,7 +896,7 @@ public class StructureManager
   StructureSettings defaultSettings = null;
 
   // TODO: [Optional] Change priority of Chimera paths
-  public static List<String> getChimeraPaths()
+  public static List<String> getChimeraPaths(boolean isChimeraX)
   {
     List<String> pathList = new ArrayList<>();
 
@@ -918,20 +918,25 @@ public class StructureManager
     /*
      * Jalview addition: check if path set in user preferences.
      */
-    String userPath = Cache.getDefault(Preferences.CHIMERA_PATH, null);
+    String userPath = Cache
+            .getDefault(isChimeraX ? Preferences.CHIMERAX_PATH
+                    : Preferences.CHIMERA_PATH, null);
     if (userPath != null)
     {
       pathList.add(0, userPath);
     }
 
+    // FIXME get an updated StructureManager for code for ChimeraX paths
+    String chimera = isChimeraX ? "ChimeraX" : "chimera";
+
     // Add default installation paths
     String os = System.getProperty("os.name");
     if (os.startsWith("Linux"))
     {
-      pathList.add("/usr/local/chimera/bin/chimera");
-      pathList.add("/usr/local/bin/chimera");
-      pathList.add("/usr/bin/chimera");
-      pathList.add(System.getProperty("user.home") + "/opt/bin/chimera");
+      pathList.add("/usr/local/chimera/bin/" + chimera);
+      pathList.add("/usr/local/bin/" + chimera);
+      pathList.add("/usr/bin/" + chimera);
+      pathList.add(System.getProperty("user.home") + "/opt/bin/" + chimera);
     }
     else if (os.startsWith("Windows"))
     {
@@ -942,15 +947,16 @@ public class StructureManager
         for (String version : new String[] { "1.11", "1.11.1", "1.11.2",
             "1.12", "1.12.1", "1.12.2", "1.13" })
         {
-          pathList.add(root + "\\Chimera " + version + "\\bin\\chimera");
+          pathList.add(root + "\\Chimera " + version + "\\bin\\" + chimera);
           pathList.add(
-                  root + "\\Chimera " + version + "\\bin\\chimera.exe");
+                  root + "\\Chimera " + version + "\\bin\\" + chimera
+                          + ".exe");
         }
       }
     }
     else if (os.startsWith("Mac"))
     {
-      pathList.add("/Applications/Chimera.app/Contents/MacOS/chimera");
+      pathList.add("/Applications/Chimera.app/Contents/MacOS/" + chimera);
     }
     return pathList;
   }
index 8f778f7..d8c8371 100644 (file)
@@ -23,7 +23,6 @@ package jalview.api.structures;
 import jalview.api.AlignmentViewPanel;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
-import jalview.schemes.ColourSchemeI;
 import jalview.structures.models.AAStructureBindingModel;
 
 public interface JalviewStructureDisplayI
@@ -59,13 +58,6 @@ public interface JalviewStructureDisplayI
   void closeViewer(boolean closeExternalViewer);
 
   /**
-   * apply a colourscheme to the structures in the viewer
-   * 
-   * @param colourScheme
-   */
-  void setJalviewColourScheme(ColourSchemeI colourScheme);
-
-  /**
    * 
    * @return true if all background sequence/structure binding threads have
    *         completed for this viewer instance
@@ -125,4 +117,48 @@ public interface JalviewStructureDisplayI
    */
   void raiseViewer();
 
+  AlignmentViewPanel getAlignmentPanel();
+
+  /**
+   * Answers true if the given alignment view is used to colour structures by
+   * sequence, false if not
+   * 
+   * @param ap
+   * @return
+   */
+  boolean isUsedForColourBy(AlignmentViewPanel ap);
+
+  /**
+   * If implemented, shows a command line console in the structure viewer
+   * 
+   * @param show
+   *          true to show, false to hide
+   */
+  void showConsole(boolean show);
+
+  /**
+   * Remove references to the given alignment view for this structure viewer
+   * 
+   * @param avp
+   */
+  void removeAlignmentPanel(AlignmentViewPanel avp);
+
+  /**
+   * Updates the progress bar if there is one. Call stopProgressBar with the
+   * returned handle to remove the message.
+   * 
+   * @param msg
+   * @return handle
+   */
+  long startProgressBar(String msg);
+
+  /**
+   * Ends the progress bar with the specified handle, leaving a message (if not
+   * null) on the status bar
+   * 
+   * @param msg
+   * @param handle
+   */
+  void stopProgressBar(String msg, long handle);
+
 }
index 0bc45e2..f4abd3d 100644 (file)
@@ -3963,7 +3963,7 @@ public class AlignFrame extends EmbmenuFrame implements ActionListener,
    * without an additional javascript library to exchange messages between the
    * distinct applets. See http://issues.jalview.org/browse/JAL-621
    * 
-   * @param viewer
+   * @param jmolViewer
    *          JmolViewer instance
    * @param sequenceIds
    *          - sequence Ids to search for associations
index 3d1442d..89a912f 100644 (file)
@@ -307,7 +307,7 @@ public class AppletJmol extends EmbmenuFrame implements
       else if (protocol == DataSourceType.FILE
               || protocol == DataSourceType.URL)
       {
-        jmb.viewer.openFile(pdbentry.getFile());
+        jmb.jmolViewer.openFile(pdbentry.getFile());
       }
       else
       {
@@ -350,7 +350,7 @@ public class AppletJmol extends EmbmenuFrame implements
             throw new Exception(MessageManager.getString(
                     "exception.invalid_datasource_couldnt_obtain_reader"));
           }
-          jmb.viewer.openReader(pdbentry.getFile(), pdbentry.getId(),
+          jmb.jmolViewer.openReader(pdbentry.getFile(), pdbentry.getId(),
                   freader);
         } catch (Exception e)
         {
@@ -406,7 +406,7 @@ public class AppletJmol extends EmbmenuFrame implements
         }
       }
     }
-    jmb.centerViewer(toshow);
+    jmb.showChains(toshow);
   }
 
   void closeViewer()
@@ -455,41 +455,41 @@ public class AppletJmol extends EmbmenuFrame implements
     else if (evt.getSource() == zappo)
     {
       setEnabled(zappo);
-      jmb.setJalviewColourScheme(new ZappoColourScheme());
+      jmb.colourByJalviewColourScheme(new ZappoColourScheme());
     }
     else if (evt.getSource() == taylor)
     {
       setEnabled(taylor);
-      jmb.setJalviewColourScheme(new TaylorColourScheme());
+      jmb.colourByJalviewColourScheme(new TaylorColourScheme());
     }
     else if (evt.getSource() == hydro)
     {
       setEnabled(hydro);
-      jmb.setJalviewColourScheme(new HydrophobicColourScheme());
+      jmb.colourByJalviewColourScheme(new HydrophobicColourScheme());
     }
     else if (evt.getSource() == helix)
     {
       setEnabled(helix);
-      jmb.setJalviewColourScheme(new HelixColourScheme());
+      jmb.colourByJalviewColourScheme(new HelixColourScheme());
     }
     else if (evt.getSource() == strand)
     {
       setEnabled(strand);
-      jmb.setJalviewColourScheme(new StrandColourScheme());
+      jmb.colourByJalviewColourScheme(new StrandColourScheme());
     }
     else if (evt.getSource() == turn)
     {
       setEnabled(turn);
-      jmb.setJalviewColourScheme(new TurnColourScheme());
+      jmb.colourByJalviewColourScheme(new TurnColourScheme());
     }
     else if (evt.getSource() == buried)
     {
       setEnabled(buried);
-      jmb.setJalviewColourScheme(new BuriedColourScheme());
+      jmb.colourByJalviewColourScheme(new BuriedColourScheme());
     }
     else if (evt.getSource() == purinepyrimidine)
     {
-      jmb.setJalviewColourScheme(new PurinePyrimidineColourScheme());
+      jmb.colourByJalviewColourScheme(new PurinePyrimidineColourScheme());
     }
     else if (evt.getSource() == user)
     {
@@ -658,7 +658,7 @@ public class AppletJmol extends EmbmenuFrame implements
     {
       currentSize = this.getSize();
 
-      if (jmb.viewer == null)
+      if (jmb.jmolViewer == null)
       {
         g.setColor(Color.black);
         g.fillRect(0, 0, currentSize.width, currentSize.height);
@@ -669,7 +669,7 @@ public class AppletJmol extends EmbmenuFrame implements
       }
       else
       {
-        jmb.viewer.renderScreenImage(g, currentSize.width,
+        jmb.jmolViewer.renderScreenImage(g, currentSize.width,
                 currentSize.height);
       }
     }
@@ -693,9 +693,9 @@ public class AppletJmol extends EmbmenuFrame implements
    * 
    * }
    */
-  public void setJalviewColourScheme(UserColourScheme ucs)
+  public void colourByJalviewColourScheme(UserColourScheme ucs)
   {
-    jmb.setJalviewColourScheme(ucs);
+    jmb.colourByJalviewColourScheme(ucs);
   }
 
   public AlignmentPanel getAlignmentPanelFor(AlignmentI alignment)
index e5767b6..9a72b2e 100644 (file)
@@ -51,13 +51,6 @@ class AppletJmolBinding extends JalviewJmolBinding
   }
 
   @Override
-  public jalview.api.FeatureRenderer getFeatureRenderer(
-          AlignmentViewPanel alignment)
-  {
-    return appletJmolBinding.ap.getFeatureRenderer();
-  }
-
-  @Override
   public jalview.api.SequenceRenderer getSequenceRenderer(
           AlignmentViewPanel alignment)
   {
@@ -154,7 +147,7 @@ class AppletJmolBinding extends JalviewJmolBinding
           Container consolePanel, String buttonsToShow)
   {
     JmolAppConsoleInterface appc = new AppletConsole();
-    appc.start(viewer);
+    appc.start(jmolViewer);
     return appc;
   }
 
@@ -181,4 +174,11 @@ class AppletJmolBinding extends JalviewJmolBinding
   {
     return null;
   }
+
+  @Override
+  protected void sendAsynchronousCommand(String command, String progressMsg)
+  {
+    // TODO Auto-generated method stub
+    
+  }
 }
index b0d3f7a..5be53a3 100644 (file)
@@ -21,7 +21,6 @@
 package jalview.appletgui;
 
 import jalview.api.AlignmentViewPanel;
-import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
@@ -76,21 +75,6 @@ public class ExtJmol extends JalviewJmolBinding
   }
 
   @Override
-  public FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment)
-  {
-    AlignmentPanel alignPanel = (AlignmentPanel) alignment;
-    if (alignPanel.av.isShowSequenceFeatures())
-    {
-      return alignPanel.getFeatureRenderer();
-    }
-    else
-    {
-      return null;
-    }
-  }
-
-
-  @Override
   public SequenceRenderer getSequenceRenderer(AlignmentViewPanel alignment)
   {
     return ((AlignmentPanel) alignment).getSequenceRenderer();
@@ -191,14 +175,16 @@ public class ExtJmol extends JalviewJmolBinding
   }
 
   @Override
-  public void releaseReferences(Object svl)
+  public Map<String, Object> getJSpecViewProperty(String arg0)
   {
+    return null;
   }
 
   @Override
-  public Map<String, Object> getJSpecViewProperty(String arg0)
+  protected void sendAsynchronousCommand(String command, String progressMsg)
   {
-    return null;
+    // TODO Auto-generated method stub
+    
   }
 
 }
index 6831a73..bfce880 100644 (file)
@@ -524,7 +524,7 @@ public class UserDefinedColours extends Panel
     }
     else if (jmol != null)
     {
-      jmol.setJalviewColourScheme(ucs);
+      jmol.colourByJalviewColourScheme(ucs);
     }
     else if (pdbcanvas != null)
     {
index 1ceabd1..fe5c980 100644 (file)
  */
 package jalview.ext.jmol;
 
-import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
-import jalview.api.SequenceRenderer;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.gui.IProgressIndicator;
+import jalview.gui.StructureViewer.ViewerType;
 import jalview.io.DataSourceType;
 import jalview.io.StructureFile;
-import jalview.schemes.ColourSchemeI;
-import jalview.schemes.ResidueProperties;
 import jalview.structure.AtomSpec;
-import jalview.structure.StructureMappingcommandSet;
+import jalview.structure.StructureCommandsI.SuperposeData;
 import jalview.structure.StructureSelectionManager;
 import jalview.structures.models.AAStructureBindingModel;
 import jalview.util.MessageManager;
 
-import java.awt.Color;
 import java.awt.Container;
 import java.awt.event.ComponentEvent;
 import java.awt.event.ComponentListener;
@@ -46,7 +42,6 @@ import java.io.File;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.BitSet;
-import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
 import java.util.StringTokenizer;
@@ -58,7 +53,6 @@ import org.jmol.api.JmolSelectionListener;
 import org.jmol.api.JmolStatusListener;
 import org.jmol.api.JmolViewer;
 import org.jmol.c.CBK;
-import org.jmol.script.T;
 import org.jmol.viewer.Viewer;
 
 public abstract class JalviewJmolBinding extends AAStructureBindingModel
@@ -67,41 +61,28 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
 {
   private String lastMessage;
 
-  boolean allChainsSelected = false;
-
   /*
    * when true, try to search the associated datamodel for sequences that are
    * associated with any unknown structures in the Jmol view.
    */
   private boolean associateNewStructs = false;
 
-  Vector<String> atomsPicked = new Vector<>();
-
-  private List<String> chainNames;
-
-  Hashtable<String, String> chainFile;
-
-  /*
-   * the default or current model displayed if the model cannot be identified
-   * from the selection message
-   */
-  int frameNo = 0;
-
-  // protected JmolGenericPopup jmolpopup; // not used - remove?
+  private Vector<String> atomsPicked = new Vector<>();
 
-  String lastCommand;
+  private String lastCommand;
 
-  boolean loadedInline;
+  private boolean loadedInline;
 
-  StringBuffer resetLastRes = new StringBuffer();
+  private StringBuffer resetLastRes = new StringBuffer();
 
-  public Viewer viewer;
+  public Viewer jmolViewer;
 
   public JalviewJmolBinding(StructureSelectionManager ssm,
           PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
           DataSourceType protocol)
   {
     super(ssm, pdbentry, sequenceIs, protocol);
+    setStructureCommands(new JmolCommands());
     /*
      * viewer = JmolViewer.allocateViewer(renderPanel, new SmarterJmolAdapter(),
      * "jalviewJmol", ap.av.applet .getDocumentBase(),
@@ -116,9 +97,10 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   {
     super(ssm, seqs);
 
-    viewer = theViewer;
-    viewer.setJmolStatusListener(this);
-    viewer.addSelectionListener(this);
+    jmolViewer = theViewer;
+    jmolViewer.setJmolStatusListener(this);
+    jmolViewer.addSelectionListener(this);
+    setStructureCommands(new JmolCommands());
   }
 
   /**
@@ -132,404 +114,31 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     return getViewerTitle("Jmol", true);
   }
 
-  /**
-   * prepare the view for a given set of models/chains. chainList contains
-   * strings of the form 'pdbfilename:Chaincode'
-   * 
-   * @param chainList
-   *          list of chains to make visible
-   */
-  public void centerViewer(Vector<String> chainList)
-  {
-    StringBuilder cmd = new StringBuilder(128);
-    int mlength, p;
-    for (String lbl : chainList)
-    {
-      mlength = 0;
-      do
-      {
-        p = mlength;
-        mlength = lbl.indexOf(":", p);
-      } while (p < mlength && mlength < (lbl.length() - 2));
-      // TODO: lookup each pdb id and recover proper model number for it.
-      cmd.append(":" + lbl.substring(mlength + 1) + " /"
-              + (1 + getModelNum(chainFile.get(lbl))) + " or ");
-    }
-    if (cmd.length() > 0)
-    {
-      cmd.setLength(cmd.length() - 4);
-    }
-    evalStateCommand("select *;restrict " + cmd + ";cartoon;center " + cmd);
-  }
-
   public void closeViewer()
   {
     // remove listeners for all structures in viewer
     getSsm().removeStructureViewerListener(this, this.getStructureFiles());
-    viewer.dispose();
+    jmolViewer.dispose();
     lastCommand = null;
-    viewer = null;
+    jmolViewer = null;
     releaseUIResources();
   }
 
   @Override
-  public void colourByChain()
-  {
-    colourBySequence = false;
-    // TODO: colour by chain should colour each chain distinctly across all
-    // visible models
-    // TODO: http://issues.jalview.org/browse/JAL-628
-    evalStateCommand("select *;color chain");
-  }
-
-  @Override
-  public void colourByCharge()
-  {
-    colourBySequence = false;
-    evalStateCommand("select *;color white;select ASP,GLU;color red;"
-            + "select LYS,ARG;color blue;select CYS;color yellow");
-  }
-
-  /**
-   * superpose the structures associated with sequences in the alignment
-   * according to their corresponding positions.
-   */
-  public void superposeStructures(AlignmentI alignment)
-  {
-    superposeStructures(alignment, -1, null);
-  }
-
-  /**
-   * superpose the structures associated with sequences in the alignment
-   * according to their corresponding positions. ded)
-   * 
-   * @param refStructure
-   *          - select which pdb file to use as reference (default is -1 - the
-   *          first structure in the alignment)
-   */
-  public void superposeStructures(AlignmentI alignment, int refStructure)
-  {
-    superposeStructures(alignment, refStructure, null);
-  }
-
-  /**
-   * superpose the structures associated with sequences in the alignment
-   * according to their corresponding positions. ded)
-   * 
-   * @param refStructure
-   *          - select which pdb file to use as reference (default is -1 - the
-   *          first structure in the alignment)
-   * @param hiddenCols
-   *          TODO
-   */
-  public void superposeStructures(AlignmentI alignment, int refStructure,
-          HiddenColumns hiddenCols)
-  {
-    superposeStructures(new AlignmentI[] { alignment },
-            new int[]
-            { refStructure }, new HiddenColumns[] { hiddenCols });
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public String superposeStructures(AlignmentI[] _alignment,
-          int[] _refStructure, HiddenColumns[] _hiddenCols)
+  public List<String> executeCommand(String command, boolean getReply)
   {
-    while (viewer.isScriptExecuting())
-    {
-      try
-      {
-        Thread.sleep(10);
-      } catch (InterruptedException i)
-      {
-      }
-    }
-
-    /*
-     * get the distinct structure files modelled
-     * (a file with multiple chains may map to multiple sequences)
-     */
-    String[] files = getStructureFiles();
-    if (!waitForFileLoad(files))
+    if (command == null)
     {
       return null;
     }
-
-    StringBuilder selectioncom = new StringBuilder(256);
-    // In principle - nSeconds specifies the speed of animation for each
-    // superposition - but is seems to behave weirdly, so we don't specify it.
-    String nSeconds = " ";
-    if (files.length > 10)
-    {
-      nSeconds = " 0.005 ";
-    }
-    else
-    {
-      nSeconds = " " + (2.0 / files.length) + " ";
-      // if (nSeconds).substring(0,5)+" ";
-    }
-
-    // see JAL-1345 - should really automatically turn off the animation for
-    // large numbers of structures, but Jmol doesn't seem to allow that.
-    // nSeconds = " ";
-    // union of all aligned positions are collected together.
-    for (int a = 0; a < _alignment.length; a++)
-    {
-      int refStructure = _refStructure[a];
-      AlignmentI alignment = _alignment[a];
-      HiddenColumns hiddenCols = _hiddenCols[a];
-      if (a > 0 && selectioncom.length() > 0 && !selectioncom
-              .substring(selectioncom.length() - 1).equals("|"))
-      {
-        selectioncom.append("|");
-      }
-      // process this alignment
-      if (refStructure >= files.length)
-      {
-        System.err.println(
-                "Invalid reference structure value " + refStructure);
-        refStructure = -1;
-      }
-
-      /*
-       * 'matched' bit j will be set for visible alignment columns j where
-       * all sequences have a residue with a mapping to the PDB structure
-       */
-      BitSet matched = new BitSet();
-      for (int m = 0; m < alignment.getWidth(); m++)
-      {
-        if (hiddenCols == null || hiddenCols.isVisible(m))
-        {
-          matched.set(m);
-        }
-      }
-
-      SuperposeData[] structures = new SuperposeData[files.length];
-      for (int f = 0; f < files.length; f++)
-      {
-        structures[f] = new SuperposeData(alignment.getWidth());
-      }
-
-      /*
-       * Calculate the superposable alignment columns ('matched'), and the
-       * corresponding structure residue positions (structures.pdbResNo)
-       */
-      int candidateRefStructure = findSuperposableResidues(alignment,
-              matched, structures);
-      if (refStructure < 0)
-      {
-        /*
-         * If no reference structure was specified, pick the first one that has
-         * a mapping in the alignment
-         */
-        refStructure = candidateRefStructure;
-      }
-
-      String[] selcom = new String[files.length];
-      int nmatched = matched.cardinality();
-      if (nmatched < 4)
-      {
-        return (MessageManager.formatMessage("label.insufficient_residues",
-                nmatched));
-      }
-
-      /*
-       * generate select statements to select regions to superimpose structures
-       */
-      {
-        // TODO extract method to construct selection statements
-        for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
-        {
-          String chainCd = ":" + structures[pdbfnum].chain;
-          int lpos = -1;
-          boolean run = false;
-          StringBuilder molsel = new StringBuilder();
-          molsel.append("{");
-
-          int nextColumnMatch = matched.nextSetBit(0);
-          while (nextColumnMatch != -1)
-          {
-            int pdbResNo = structures[pdbfnum].pdbResNo[nextColumnMatch];
-            if (lpos != pdbResNo - 1)
-            {
-              // discontinuity
-              if (lpos != -1)
-              {
-                molsel.append(lpos);
-                molsel.append(chainCd);
-                molsel.append("|");
-              }
-              run = false;
-            }
-            else
-            {
-              // continuous run - and lpos >-1
-              if (!run)
-              {
-                // at the beginning, so add dash
-                molsel.append(lpos);
-                molsel.append("-");
-              }
-              run = true;
-            }
-            lpos = pdbResNo;
-            nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1);
-          }
-          /*
-           * add final selection phrase
-           */
-          if (lpos != -1)
-          {
-            molsel.append(lpos);
-            molsel.append(chainCd);
-            molsel.append("}");
-          }
-          if (molsel.length() > 1)
-          {
-            selcom[pdbfnum] = molsel.toString();
-            selectioncom.append("((");
-            selectioncom.append(selcom[pdbfnum].substring(1,
-                    selcom[pdbfnum].length() - 1));
-            selectioncom.append(" )& ");
-            selectioncom.append(pdbfnum + 1);
-            selectioncom.append(".1)");
-            if (pdbfnum < files.length - 1)
-            {
-              selectioncom.append("|");
-            }
-          }
-          else
-          {
-            selcom[pdbfnum] = null;
-          }
-        }
-      }
-      StringBuilder command = new StringBuilder(256);
-      // command.append("set spinFps 10;\n");
-
-      for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
-      {
-        if (pdbfnum == refStructure || selcom[pdbfnum] == null
-                || selcom[refStructure] == null)
-        {
-          continue;
-        }
-        command.append("echo ");
-        command.append("\"Superposing (");
-        command.append(structures[pdbfnum].pdbId);
-        command.append(") against reference (");
-        command.append(structures[refStructure].pdbId);
-        command.append(")\";\ncompare " + nSeconds);
-        command.append("{");
-        command.append(Integer.toString(1 + pdbfnum));
-        command.append(".1} {");
-        command.append(Integer.toString(1 + refStructure));
-        // conformation=1 excludes alternate locations for CA (JAL-1757)
-        command.append(
-                ".1} SUBSET {(*.CA | *.P) and conformation=1} ATOMS ");
-
-        // for (int s = 0; s < 2; s++)
-        // {
-        // command.append(selcom[(s == 0 ? pdbfnum : refStructure)]);
-        // }
-        command.append(selcom[pdbfnum]);
-        command.append(selcom[refStructure]);
-        command.append(" ROTATE TRANSLATE;\n");
-      }
-      if (selectioncom.length() > 0)
-      {
-        // TODO is performing selectioncom redundant here? is done later on
-        // System.out.println("Select regions:\n" + selectioncom.toString());
-        evalStateCommand("select *; cartoons off; backbone; select ("
-                + selectioncom.toString() + "); cartoons; ");
-        // selcom.append("; ribbons; ");
-        String cmdString = command.toString();
-        // System.out.println("Superimpose command(s):\n" + cmdString);
-
-        evalStateCommand(cmdString);
-      }
-    }
-    if (selectioncom.length() > 0)
-    {// finally, mark all regions that were superposed.
-      if (selectioncom.substring(selectioncom.length() - 1).equals("|"))
-      {
-        selectioncom.setLength(selectioncom.length() - 1);
-      }
-      // System.out.println("Select regions:\n" + selectioncom.toString());
-      evalStateCommand("select *; cartoons off; backbone; select ("
-              + selectioncom.toString() + "); cartoons; ");
-      // evalStateCommand("select *; backbone; select "+selcom.toString()+";
-      // cartoons; center "+selcom.toString());
-    }
-
-    return null;
-  }
-
-  public void evalStateCommand(String command)
-  {
     jmolHistory(false);
     if (lastCommand == null || !lastCommand.equals(command))
     {
-      viewer.evalStringQuiet(command + "\n");
+      jmolViewer.evalStringQuiet(command + "\n");
     }
     jmolHistory(true);
     lastCommand = command;
-  }
-
-  Thread colourby = null;
-  /**
-   * Sends a set of colour commands to the structure viewer
-   * 
-   * @param colourBySequenceCommands
-   */
-  @Override
-  protected void colourBySequence(
-          final StructureMappingcommandSet[] colourBySequenceCommands)
-  {
-    if (colourby != null)
-    {
-      colourby.interrupt();
-      colourby = null;
-    }
-    colourby = new Thread(new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        for (StructureMappingcommandSet cpdbbyseq : colourBySequenceCommands)
-        {
-          for (String cbyseq : cpdbbyseq.commands)
-          {
-            executeWhenReady(cbyseq);
-          }
-        }
-      }
-    });
-    colourby.start();
-  }
-
-  /**
-   * @param files
-   * @param sr
-   * @param viewPanel
-   * @return
-   */
-  @Override
-  protected StructureMappingcommandSet[] getColourBySequenceCommands(
-          String[] files, SequenceRenderer sr, AlignmentViewPanel viewPanel)
-  {
-    return JmolCommands.getColourBySequenceCommand(getSsm(), files,
-            getSequence(), sr, viewPanel);
-  }
-
-  /**
-   * @param command
-   */
-  protected void executeWhenReady(String command)
-  {
-    evalStateCommand(command);
+    return null;
   }
 
   public void createImage(String file, String type, int quality)
@@ -570,43 +179,6 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     return null;
   }
 
-  public Color getColour(int atomIndex, int pdbResNum, String chain,
-          String pdbfile)
-  {
-    if (getModelNum(pdbfile) < 0)
-    {
-      return null;
-    }
-    // TODO: verify atomIndex is selecting correct model.
-    // return new Color(viewer.getAtomArgb(atomIndex)); Jmol 12.2.4
-    int colour = viewer.ms.at[atomIndex].atomPropertyInt(T.color);
-    return new Color(colour);
-  }
-
-  /**
-   * instruct the Jalview binding to update the pdbentries vector if necessary
-   * prior to matching the jmol view's contents to the list of structure files
-   * Jalview knows about.
-   */
-  public abstract void refreshPdbEntries();
-
-  private int getModelNum(String modelFileName)
-  {
-    String[] mfn = getStructureFiles();
-    if (mfn == null)
-    {
-      return -1;
-    }
-    for (int i = 0; i < mfn.length; i++)
-    {
-      if (mfn[i].equalsIgnoreCase(modelFileName))
-      {
-        return i;
-      }
-    }
-    return -1;
-  }
-
   /**
    * map between index of model filename returned from getPdbFile and the first
    * index of models from this file in the viewer. Note - this is not trimmed -
@@ -618,18 +190,18 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   public synchronized String[] getStructureFiles()
   {
     List<String> mset = new ArrayList<>();
-    if (viewer == null)
+    if (jmolViewer == null)
     {
       return new String[0];
     }
 
     if (modelFileNames == null)
     {
-      int modelCount = viewer.ms.mc;
+      int modelCount = jmolViewer.ms.mc;
       String filePath = null;
       for (int i = 0; i < modelCount; ++i)
       {
-        filePath = viewer.ms.getModelFileName(i);
+        filePath = jmolViewer.ms.getModelFileName(i);
         if (!mset.contains(filePath))
         {
           mset.add(filePath);
@@ -670,7 +242,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     {
       if (resetLastRes.length() > 0)
       {
-        viewer.evalStringQuiet(resetLastRes.toString());
+        jmolViewer.evalStringQuiet(resetLastRes.toString());
         resetLastRes.setLength(0);
       }
       for (AtomSpec atom : atoms)
@@ -706,9 +278,9 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     jmolHistory(false);
 
     StringBuilder cmd = new StringBuilder(64);
-    cmd.append("select " + pdbResNum); // +modelNum
+    cmd.append("select ").append(String.valueOf(pdbResNum)); // +modelNum
 
-    resetLastRes.append("select " + pdbResNum); // +modelNum
+    resetLastRes.append("select ").append(String.valueOf(pdbResNum)); // +modelNum
 
     cmd.append(":");
     resetLastRes.append(":");
@@ -718,8 +290,8 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
       resetLastRes.append(chain);
     }
     {
-      cmd.append(" /" + (mdlNum + 1));
-      resetLastRes.append("/" + (mdlNum + 1));
+      cmd.append(" /").append(String.valueOf(mdlNum + 1));
+      resetLastRes.append("/").append(String.valueOf(mdlNum + 1));
     }
     cmd.append(";wireframe 100;" + cmd.toString() + " and not hetero;");
 
@@ -728,16 +300,16 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
 
     cmd.append("spacefill 200;select none");
 
-    viewer.evalStringQuiet(cmd.toString());
+    jmolViewer.evalStringQuiet(cmd.toString());
     jmolHistory(true);
 
   }
 
-  boolean debug = true;
+  private boolean debug = true;
 
   private void jmolHistory(boolean enable)
   {
-    viewer.evalStringQuiet("History " + ((debug || enable) ? "on" : "off"));
+    jmolViewer.evalStringQuiet("History " + ((debug || enable) ? "on" : "off"));
   }
 
   public void loadInline(String string)
@@ -751,7 +323,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     // Then, construct pass a reader for the string to Jmol.
     // ((org.jmol.Viewer.Viewer) viewer).loadModelFromFile(fullPathName,
     // fileName, null, reader, false, null, null, 0);
-    viewer.openStringInline(string);
+    jmolViewer.openStringInline(string);
   }
 
   protected void mouseOverStructure(int atomIndex, final String strInfo)
@@ -794,8 +366,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
       chainId = " ";
     }
 
-    String pdbfilename = modelFileNames[frameNo]; // default is first or current
-    // model
+    String pdbfilename = modelFileNames[0]; // default is first model
     if (mdlSep > -1)
     {
       if (chainSeparator1 == -1)
@@ -828,7 +399,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
 
           if (pdbfilename == null)
           {
-            pdbfilename = new File(viewer.ms.getModelFileName(mnumber))
+            pdbfilename = new File(jmolViewer.ms.getModelFileName(mnumber))
                     .getAbsolutePath();
           }
         }
@@ -854,7 +425,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
       sb.append(";set hoverLabel \"").append(toks.nextToken()).append(" ")
               .append(toks.nextToken());
       sb.append("|").append(label).append("\"");
-      evalStateCommand(sb.toString());
+      executeCommand(sb.toString(), false);
     }
   }
 
@@ -915,12 +486,12 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
 
     if (!atomsPicked.contains(picked))
     {
-      viewer.evalStringQuiet("select " + picked + ";label %n %r:%c");
+      jmolViewer.evalStringQuiet("select " + picked + ";label %n %r:%c");
       atomsPicked.addElement(picked);
     }
     else
     {
-      viewer.evalString("select " + picked + ";label off");
+      jmolViewer.evalString("select " + picked + ";label off");
       atomsPicked.removeElement(picked);
     }
     jmolHistory(true);
@@ -1036,8 +607,6 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     fileLoadingError = null;
     String[] oldmodels = modelFileNames;
     modelFileNames = null;
-    chainNames = new ArrayList<>();
-    chainFile = new Hashtable<>();
     boolean notifyLoaded = false;
     String[] modelfilenames = getStructureFiles();
     // first check if we've lost any structures
@@ -1088,7 +657,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
         // calculate essential attributes for the pdb data imported inline.
         // prolly need to resolve modelnumber properly - for now just use our
         // 'best guess'
-        pdbfile = viewer.getData(
+        pdbfile = jmolViewer.getData(
                 "" + (1 + _modelFileNameMap[modelnum]) + ".0", "PDB");
       }
       // search pdbentries and sequences to find correct pdbentry for this
@@ -1146,10 +715,10 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
           // add an entry for every chain in the model
           for (int i = 0; i < pdb.getChains().size(); i++)
           {
-            String chid = new String(
-                    pdb.getId() + ":" + pdb.getChains().elementAt(i).id);
-            chainFile.put(chid, fileName);
-            chainNames.add(chid);
+            String chid = pdb.getId() + ":"
+                    + pdb.getChains().elementAt(i).id;
+            addChainFile(chid, fileName);
+            getChainNames().add(chid);
           }
           notifyLoaded = true;
         }
@@ -1160,7 +729,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
         // this is a foreign pdb file that jalview doesn't know about - add
         // it to the dataset and try to find a home - either on a matching
         // sequence or as a new sequence.
-        String pdbcontent = viewer.getData("/" + (modelnum + 1) + ".1",
+        String pdbcontent = jmolViewer.getData("/" + (modelnum + 1) + ".1",
                 "PDB");
         // parse pdb file into a chain, etc.
         // locate best match for pdb in associated views and add mapping to
@@ -1179,7 +748,7 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     // }
     if (!isLoadingFromArchive())
     {
-      viewer.evalStringQuiet(
+      jmolViewer.evalStringQuiet(
               "model *; select backbone;restrict;cartoon;wireframe off;spacefill off");
     }
     // register ourselves as a listener and notify the gui that it needs to
@@ -1198,12 +767,6 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     setLoadingFromArchive(false);
   }
 
-  @Override
-  public List<String> getChainNames()
-  {
-    return chainNames;
-  }
-
   protected IProgressIndicator getIProgressIndicator()
   {
     return null;
@@ -1248,35 +811,6 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
 
   }
 
-  @Override
-  public void setJalviewColourScheme(ColourSchemeI cs)
-  {
-    colourBySequence = false;
-
-    if (cs == null)
-    {
-      return;
-    }
-
-    jmolHistory(false);
-    StringBuilder command = new StringBuilder(128);
-    command.append("select *;color white;");
-    List<String> residueSet = ResidueProperties.getResidues(isNucleotide(),
-            false);
-    for (String resName : residueSet)
-    {
-      char res = resName.length() == 3
-              ? ResidueProperties.getSingleCharacterCode(resName)
-              : resName.charAt(0);
-      Color col = cs.findColour(res, 0, null, null, 0f);
-      command.append("select " + resName + ";color[" + col.getRed() + ","
-              + col.getGreen() + "," + col.getBlue() + "];");
-    }
-
-    evalStateCommand(command.toString());
-    jmolHistory(true);
-  }
-
   public void showHelp()
   {
     showUrl("http://jmol.sourceforge.net/docs/JmolUserGuide/", "jmolHelp");
@@ -1290,13 +824,6 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   public abstract void showUrl(String url, String target);
 
   /**
-   * called when the binding thinks the UI needs to be refreshed after a Jmol
-   * state change. this could be because structures were loaded, or because an
-   * error has occured.
-   */
-  public abstract void refreshGUI();
-
-  /**
    * called to show or hide the associated console window container.
    * 
    * @param show
@@ -1345,12 +872,12 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
     {
       commandOptions = "";
     }
-    viewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
+    jmolViewer = (Viewer) JmolViewer.allocateViewer(renderPanel,
             (jmolfileio ? new SmarterJmolAdapter() : null),
             htmlName + ((Object) this).toString(), documentBase, codeBase,
             commandOptions, this);
 
-    viewer.setJmolStatusListener(this); // extends JmolCallbackListener
+    jmolViewer.setJmolStatusListener(this); // extends JmolCallbackListener
 
     console = createJmolConsole(consolePanel, buttonsToShow);
     if (consolePanel != null)
@@ -1367,15 +894,6 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   protected org.jmol.api.JmolAppConsoleInterface console = null;
 
   @Override
-  public void setBackgroundColour(java.awt.Color col)
-  {
-    jmolHistory(false);
-    viewer.evalStringQuiet("background [" + col.getRed() + ","
-            + col.getGreen() + "," + col.getBlue() + "];");
-    jmolHistory(true);
-  }
-
-  @Override
   public int[] resizeInnerPanel(String data)
   {
     // Jalview doesn't honour resize panel requests
@@ -1435,4 +953,27 @@ public abstract class JalviewJmolBinding extends AAStructureBindingModel
   {
     showConsole(false);
   }
+
+  @Override
+  protected int getModelNoForFile(String pdbFile)
+  {
+    if (modelFileNames == null)
+    {
+      return -1;
+    }
+    for (int i = 0; i < modelFileNames.length; i++)
+    {
+      if (modelFileNames[i].equalsIgnoreCase(pdbFile))
+      {
+        return i;
+      }
+    }
+    return -1;
+  }
+
+  @Override
+  protected ViewerType getViewerType()
+  {
+    return ViewerType.JMOL;
+  }
 }
index 8fb0de6..6f682be 100644 (file)
@@ -28,49 +28,83 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.seqfeatures.FeatureColourFinder;
+import jalview.structure.AtomSpecModel;
+import jalview.structure.StructureCommandsBase;
 import jalview.structure.StructureMapping;
-import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
+import jalview.util.Comparison;
 
 import java.awt.Color;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /**
- * Routines for generating Jmol commands for Jalview/Jmol binding another
- * cruisecontrol test.
+ * Routines for generating Jmol commands for Jalview/Jmol binding
  * 
  * @author JimP
  * 
  */
-public class JmolCommands
+public class JmolCommands extends StructureCommandsBase
 {
+  private static final String CMD_COLOUR_BY_CHARGE = "select *;color white;select ASP,GLU;color red;"
+          + "select LYS,ARG;color blue;select CYS;color yellow";
+
+  private static final String CMD_COLOUR_BY_CHAIN = "select *;color chain";
+
+  private static final String PIPE = "|";
+
+  private static final String HYPHEN = "-";
+
+  private static final String COLON = ":";
+
+  private static final String SLASH = "/";
 
   /**
-   * Jmol utility which constructs the commands to colour chains by the given
-   * alignment
+   * {@inheritDoc}
    * 
-   * @returns Object[] { Object[] { <model being coloured>,
+   * @return
+   */
+  @Override
+  public int getModelStartNo()
+  {
+    return 1;
+  }
+
+  /**
+   * Returns a string representation of the given colour suitable for inclusion
+   * in Jmol commands
    * 
+   * @param c
+   * @return
    */
-  public static StructureMappingcommandSet[] getColourBySequenceCommand(
-          StructureSelectionManager ssm, String[] files,
+  protected String getColourString(Color c)
+  {
+    return c == null ? null
+            : String.format("[%d,%d,%d]", c.getRed(), c.getGreen(),
+                    c.getBlue());
+  }
+
+
+  public String[] colourBySequence(StructureSelectionManager ssm,
+          String[] files,
           SequenceI[][] sequence, SequenceRenderer sr,
           AlignmentViewPanel viewPanel)
   {
+    // TODO delete method
+
     FeatureRenderer fr = viewPanel.getFeatureRenderer();
     FeatureColourFinder finder = new FeatureColourFinder(fr);
     AlignViewportI viewport = viewPanel.getAlignViewport();
     HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
     AlignmentI al = viewport.getAlignment();
-    List<StructureMappingcommandSet> cset = new ArrayList<StructureMappingcommandSet>();
+    List<String> cset = new ArrayList<>();
 
     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
     {
       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
-      StringBuffer command = new StringBuffer();
-      StructureMappingcommandSet smc;
-      ArrayList<String> str = new ArrayList<String>();
+      StringBuilder command = new StringBuilder(128);
+      List<String> str = new ArrayList<>();
 
       if (mapping == null || mapping.length < 1)
       {
@@ -89,7 +123,7 @@ public class JmolCommands
             for (int r = 0; r < asp.getLength(); r++)
             {
               // no mapping to gaps in sequence
-              if (jalview.util.Comparison.isGap(asp.getCharAt(r)))
+              if (Comparison.isGap(asp.getCharAt(r)))
               {
                 continue;
               }
@@ -127,9 +161,8 @@ public class JmolCommands
 
               String newSelcom = (mapping[m].getChain() != " "
                       ? ":" + mapping[m].getChain()
-                      : "") + "/" + (pdbfnum + 1) + ".1" + ";color["
-                      + col.getRed() + "," + col.getGreen() + ","
-                      + col.getBlue() + "]";
+                      : "") + "/" + (pdbfnum + 1) + ".1" + ";color"
+                      + getColourString(col);
               if (command.length() > newSelcom.length() && command
                       .substring(command.length() - newSelcom.length())
                       .equals(newSelcom))
@@ -164,15 +197,14 @@ public class JmolCommands
         str.add(command.toString());
         command.setLength(0);
       }
-      // Finally, add the command set ready to be returned.
-      cset.add(new StructureMappingcommandSet(JmolCommands.class,
-              files[pdbfnum], str.toArray(new String[str.size()])));
+      cset.addAll(str);
 
     }
-    return cset.toArray(new StructureMappingcommandSet[cset.size()]);
+    return cset.toArray(new String[cset.size()]);
   }
 
-  public static StringBuffer condenseCommand(StringBuffer command, int pos)
+  public static StringBuilder condenseCommand(StringBuilder command,
+          int pos)
   {
 
     // work back to last 'select'
@@ -187,7 +219,7 @@ public class JmolCommands
       ;
     } while ((q = command.indexOf("select", p)) == -1 && p > 0);
 
-    StringBuffer sb = new StringBuffer(command.substring(0, q + 7));
+    StringBuilder sb = new StringBuilder(command.substring(0, q + 7));
 
     command = command.delete(0, q + 7);
 
@@ -207,4 +239,196 @@ public class JmolCommands
     return sb;
   }
 
+  @Override
+  public String colourByChain()
+  {
+    return CMD_COLOUR_BY_CHAIN;
+  }
+
+  @Override
+  public String colourByCharge()
+  {
+    return CMD_COLOUR_BY_CHARGE;
+  }
+
+  @Override
+  public String colourByResidues(Map<String, Color> colours)
+  {
+    StringBuilder cmd = new StringBuilder(128);
+    cmd.append("select *;color white;");
+    cmd.append(super.colourByResidues(colours));
+
+    return cmd.toString();
+  }
+
+  @Override
+  public String setBackgroundColour(Color col)
+  {
+    return "background " + getColourString(col);
+  }
+
+  @Override
+  public String focusView()
+  {
+    return "zoom 0";
+  }
+
+  @Override
+  public String showChains(List<String> toShow)
+  {
+    StringBuilder atomSpec = new StringBuilder(128);
+    boolean first = true;
+    for (String chain : toShow)
+    {
+      String[] tokens = chain.split(":");
+      if (tokens.length == 2)
+      {
+        if (!first)
+        {
+          atomSpec.append(" or ");
+        }
+        first = false;
+        atomSpec.append(":").append(tokens[1]).append(" /").append(tokens[0]);
+      }
+    }
+
+    String spec = atomSpec.toString();
+    String command = "select *;restrict " + spec + ";cartoon;center "
+            + spec;
+    return command;
+  }
+
+  /**
+   * Returns a command to superpose atoms in {@code atomSpec} to those in
+   * {@code refAtoms}, restricted to alpha carbons only (Phosphorous for rna).
+   * For example
+   * 
+   * <pre>
+   * compare {2.1} {1.1} SUBSET {(*.CA | *.P) and conformation=1} 
+   *         ATOMS {1-87:A}{2-54:A|61-94:A} ROTATE TRANSLATE 1.0;
+   * </pre>
+   * 
+   * where {@code conformation=1} excludes ALTLOC atom locations, and 1.0 is the
+   * time in seconds to animate the action. For this example, atoms in model 2
+   * are moved towards atoms in model 1.
+   * <p>
+   * The two atomspecs should each be for one model only, but may have more than
+   * one chain. The number of atoms specified should be the same for both
+   * models, though if not, Jmol may make a 'best effort' at superposition.
+   * 
+   * @see https://chemapps.stolaf.edu/jmol/docs/#compare
+   */
+  @Override
+  public String superposeStructures(AtomSpecModel refAtoms,
+          AtomSpecModel atomSpec)
+  {
+    StringBuilder sb = new StringBuilder(64);
+    int refModel = refAtoms.getModels().iterator().next();
+    int model2 = atomSpec.getModels().iterator().next();
+    sb.append(String.format("compare {%d.1} {%d.1}", model2, refModel));
+    sb.append(" SUBSET {(*.CA | *.P) and conformation=1} ATOMS {");
+
+    /*
+     * command examples don't include modelspec with atoms, getAtomSpec does;
+     * it works, so leave it as it is for simplicity
+     */
+    sb.append(getAtomSpec(atomSpec, true)).append("}{");
+    sb.append(getAtomSpec(refAtoms, true)).append("}");
+    sb.append(" ROTATE TRANSLATE ");
+    sb.append(getCommandSeparator());
+
+    /*
+     * show residues used for superposition as ribbon
+     */
+    sb.append("select ").append(getAtomSpec(atomSpec, false)).append("|");
+    sb.append(getAtomSpec(refAtoms, false)).append(getCommandSeparator())
+            .append("cartoons");
+
+    return sb.toString();
+  }
+
+  @Override
+  public String openCommandFile(String path)
+  {
+    /*
+     * https://chemapps.stolaf.edu/jmol/docs/#script
+     * not currently used in Jalview
+     */
+    return "script " + path;
+  }
+
+  @Override
+  public String saveSession(String filepath)
+  {
+    /*
+     * https://chemapps.stolaf.edu/jmol/docs/#write
+     * not currently used in Jalview
+     */
+    return "write \"" + filepath + "\"";
+  }
+
+  @Override
+  protected String getColourCommand(String atomSpec, Color colour)
+  {
+    StringBuilder sb = new StringBuilder(atomSpec.length()+20);
+    sb.append("select ").append(atomSpec).append(getCommandSeparator())
+            .append("color").append(getColourString(colour));
+    return sb.toString();
+  }
+
+  @Override
+  protected String getResidueSpec(String residue)
+  {
+    return residue;
+  }
+
+  /**
+   * Generates a Jmol atomspec string like
+   * 
+   * <pre>
+   * 2-5:A/1.1,8:A/1.1,5-10:B/2.1
+   * </pre>
+   * 
+   * Parameter {@code alphaOnly} is not used here - this restriction is made by
+   * a separate clause in the {@code compare} (superposition) command.
+   */
+  @Override
+  public String getAtomSpec(AtomSpecModel model, boolean alphaOnly)
+  {
+    StringBuilder sb = new StringBuilder(128);
+
+    boolean first = true;
+    for (int modelNo : model.getModels())
+    {
+      for (String chain : model.getChains(modelNo))
+      {
+        for (int[] range : model.getRanges(modelNo, chain))
+        {
+          if (!first)
+          {
+            sb.append(PIPE);
+          }
+          first = false;
+          if (range[0] == range[1])
+          {
+            sb.append(range[0]);
+          }
+          else
+          {
+            sb.append(range[0]).append(HYPHEN).append(range[1]);
+          }
+          sb.append(COLON).append(chain.trim()).append(SLASH);
+          sb.append(String.valueOf(modelNo)).append(".1");
+        }
+      }
+    }
+
+    return sb.toString();
+  }
+
+  @Override
+  public String showBackbone()
+  {
+    return "select *; cartoons off; backbone";
+  }
 }
diff --git a/src/jalview/ext/rbvi/chimera/AtomSpecModel.java b/src/jalview/ext/rbvi/chimera/AtomSpecModel.java
deleted file mode 100644 (file)
index 39d6704..0000000
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * 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.ext.rbvi.chimera;
-
-import jalview.util.IntRangeComparator;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-/**
- * A class to model a Chimera atomspec pattern, for example
- * 
- * <pre>
- * #0:15.A,28.A,54.A,63.A,70-72.A,83-84.A,97-98.A|#1:2.A,6.A,11.A,13-14.A,70.A,82.A,96-97.A
- * </pre>
- * 
- * where
- * <ul>
- * <li>#0 is a model number</li>
- * <li>15 or 70-72 is a residue number, or range of residue numbers</li>
- * <li>.A is a chain identifier</li>
- * <li>residue ranges are separated by comma</li>
- * <li>atomspecs for distinct models are separated by | (or)</li>
- * </ul>
- * 
- * <pre>
- * &#64;see http://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec.html
- * </pre>
- */
-public class AtomSpecModel
-{
-  private Map<Integer, Map<String, List<int[]>>> atomSpec;
-
-  /**
-   * Constructor
-   */
-  public AtomSpecModel()
-  {
-    atomSpec = new TreeMap<Integer, Map<String, List<int[]>>>();
-  }
-
-  /**
-   * Adds one contiguous range to this atom spec
-   * 
-   * @param model
-   * @param startPos
-   * @param endPos
-   * @param chain
-   */
-  public void addRange(int model, int startPos, int endPos, String chain)
-  {
-    /*
-     * Get/initialize map of data for the colour and model
-     */
-    Map<String, List<int[]>> modelData = atomSpec.get(model);
-    if (modelData == null)
-    {
-      atomSpec.put(model, modelData = new TreeMap<String, List<int[]>>());
-    }
-
-    /*
-     * Get/initialize map of data for colour, model and chain
-     */
-    List<int[]> chainData = modelData.get(chain);
-    if (chainData == null)
-    {
-      chainData = new ArrayList<int[]>();
-      modelData.put(chain, chainData);
-    }
-
-    /*
-     * Add the start/end positions
-     */
-    chainData.add(new int[] { startPos, endPos });
-    // TODO add intelligently, using a RangeList class
-  }
-
-  /**
-   * Returns the range(s) formatted as a Chimera atomspec
-   * 
-   * @return
-   */
-  public String getAtomSpec()
-  {
-    StringBuilder sb = new StringBuilder(128);
-    boolean firstModel = true;
-    for (Integer model : atomSpec.keySet())
-    {
-      if (!firstModel)
-      {
-        sb.append("|");
-      }
-      firstModel = false;
-      sb.append("#").append(model).append(":");
-
-      boolean firstPositionForModel = true;
-      final Map<String, List<int[]>> modelData = atomSpec.get(model);
-
-      for (String chain : modelData.keySet())
-      {
-        chain = " ".equals(chain) ? chain : chain.trim();
-
-        List<int[]> rangeList = modelData.get(chain);
-
-        /*
-         * sort ranges into ascending start position order
-         */
-        Collections.sort(rangeList, IntRangeComparator.ASCENDING);
-
-        int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0];
-        int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1];
-
-        Iterator<int[]> iterator = rangeList.iterator();
-        while (iterator.hasNext())
-        {
-          int[] range = iterator.next();
-          if (range[0] <= end + 1)
-          {
-            /*
-             * range overlaps or is contiguous with the last one
-             * - so just extend the end position, and carry on
-             * (unless this is the last in the list)
-             */
-            end = Math.max(end, range[1]);
-          }
-          else
-          {
-            /*
-             * we have a break so append the last range
-             */
-            appendRange(sb, start, end, chain, firstPositionForModel);
-            firstPositionForModel = false;
-            start = range[0];
-            end = range[1];
-          }
-        }
-
-        /*
-         * and append the last range
-         */
-        if (!rangeList.isEmpty())
-        {
-          appendRange(sb, start, end, chain, firstPositionForModel);
-          firstPositionForModel = false;
-        }
-      }
-    }
-    return sb.toString();
-  }
-
-  /**
-   * @param sb
-   * @param start
-   * @param end
-   * @param chain
-   * @param firstPositionForModel
-   */
-  protected void appendRange(StringBuilder sb, int start, int end,
-          String chain, boolean firstPositionForModel)
-  {
-    if (!firstPositionForModel)
-    {
-      sb.append(",");
-    }
-    if (end == start)
-    {
-      sb.append(start);
-    }
-    else
-    {
-      sb.append(start).append("-").append(end);
-    }
-
-    sb.append(".");
-    if (!" ".equals(chain)) {
-      sb.append(chain);
-    }
-  }
-}
index 3caaac3..1b1dd35 100644 (file)
@@ -23,23 +23,23 @@ package jalview.ext.rbvi.chimera;
 import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
-import jalview.api.SequenceRenderer;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.MappedFeatures;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.gui.Desktop;
-import jalview.renderer.seqfeatures.FeatureColourFinder;
+import jalview.structure.AtomSpecModel;
+import jalview.structure.StructureCommandsBase;
 import jalview.structure.StructureMapping;
-import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.ColorUtils;
-import jalview.util.Comparison;
+import jalview.util.IntRangeComparator;
 
 import java.awt.Color;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -50,274 +50,40 @@ import java.util.Map;
  * @author JimP
  * 
  */
-public class ChimeraCommands
+public class ChimeraCommands extends StructureCommandsBase
 {
-
   public static final String NAMESPACE_PREFIX = "jv_";
 
-  /**
-   * Constructs Chimera commands to colour residues as per the Jalview alignment
-   * 
-   * @param ssm
-   * @param files
-   * @param sequence
-   * @param sr
-   * @param fr
-   * @param viewPanel
-   * @return
-   */
-  public static StructureMappingcommandSet[] getColourBySequenceCommand(
-          StructureSelectionManager ssm, String[] files,
-          SequenceI[][] sequence, SequenceRenderer sr,
-          AlignmentViewPanel viewPanel)
-  {
-    Map<Object, AtomSpecModel> colourMap = buildColoursMap(ssm, files,
-            sequence, sr, viewPanel);
-
-    List<String> colourCommands = buildColourCommands(colourMap);
-
-    StructureMappingcommandSet cs = new StructureMappingcommandSet(
-            ChimeraCommands.class, null,
-            colourCommands.toArray(new String[colourCommands.size()]));
-
-    return new StructureMappingcommandSet[] { cs };
-  }
-
-  /**
-   * Traverse the map of colours/models/chains/positions to construct a list of
-   * 'color' commands (one per distinct colour used). The format of each command
-   * is
-   * 
-   * <pre>
-   * <blockquote> 
-   * color colorname #modelnumber:range.chain 
-   * e.g. color #00ff00 #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,...
-   * </blockquote>
-   * </pre>
-   * 
-   * @param colourMap
-   * @return
-   */
-  protected static List<String> buildColourCommands(
-          Map<Object, AtomSpecModel> colourMap)
-  {
-    /*
-     * This version concatenates all commands into a single String (semi-colon
-     * delimited). If length limit issues arise, refactor to return one color
-     * command per colour.
-     */
-    List<String> commands = new ArrayList<>();
-    StringBuilder sb = new StringBuilder(256);
-    boolean firstColour = true;
-    for (Object key : colourMap.keySet())
-    {
-      Color colour = (Color) key;
-      String colourCode = ColorUtils.toTkCode(colour);
-      if (!firstColour)
-      {
-        sb.append("; ");
-      }
-      sb.append("color ").append(colourCode).append(" ");
-      firstColour = false;
-      final AtomSpecModel colourData = colourMap.get(colour);
-      sb.append(colourData.getAtomSpec());
-    }
-    commands.add(sb.toString());
-    return commands;
-  }
+  private static final String CMD_COLOUR_BY_CHARGE = "color white;color red ::ASP;color red ::GLU;color blue ::LYS;color blue ::ARG;color yellow ::CYS";
 
-  /**
-   * Traverses a map of { modelNumber, {chain, {list of from-to ranges} } } and
-   * builds a Chimera format atom spec
-   * 
-   * @param modelAndChainRanges
-   */
-  protected static String getAtomSpec(
-          Map<Integer, Map<String, List<int[]>>> modelAndChainRanges)
-  {
-    StringBuilder sb = new StringBuilder(128);
-    boolean firstModelForColour = true;
-    for (Integer model : modelAndChainRanges.keySet())
-    {
-      boolean firstPositionForModel = true;
-      if (!firstModelForColour)
-      {
-        sb.append("|");
-      }
-      firstModelForColour = false;
-      sb.append("#").append(model).append(":");
+  private static final String CMD_COLOUR_BY_CHAIN = "rainbow chain";
 
-      final Map<String, List<int[]>> modelData = modelAndChainRanges
-              .get(model);
-      for (String chain : modelData.keySet())
-      {
-        boolean hasChain = !"".equals(chain.trim());
-        for (int[] range : modelData.get(chain))
-        {
-          if (!firstPositionForModel)
-          {
-            sb.append(",");
-          }
-          if (range[0] == range[1])
-          {
-            sb.append(range[0]);
-          }
-          else
-          {
-            sb.append(range[0]).append("-").append(range[1]);
-          }
-          if (hasChain)
-          {
-            sb.append(".").append(chain);
-          }
-          firstPositionForModel = false;
-        }
-      }
-    }
-    return sb.toString();
-  }
+  // Chimera clause to exclude alternate locations in atom selection
+  private static final String NO_ALTLOCS = "&~@.B-Z&~@.2-9";
 
-  /**
-   * <pre>
-   * Build a data structure which records contiguous subsequences for each colour. 
-   * From this we can easily generate the Chimera command for colour by sequence.
-   * Color
-   *     Model number
-   *         Chain
-   *             list of start/end ranges
-   * Ordering is by order of addition (for colours and positions), natural ordering (for models and chains)
-   * </pre>
-   */
-  protected static Map<Object, AtomSpecModel> buildColoursMap(
-          StructureSelectionManager ssm, String[] files,
-          SequenceI[][] sequence, SequenceRenderer sr,
-          AlignmentViewPanel viewPanel)
+  @Override
+  public String getColourCommand(String atomSpec, Color colour)
   {
-    FeatureRenderer fr = viewPanel.getFeatureRenderer();
-    FeatureColourFinder finder = new FeatureColourFinder(fr);
-    AlignViewportI viewport = viewPanel.getAlignViewport();
-    HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
-    AlignmentI al = viewport.getAlignment();
-    Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<>();
-    Color lastColour = null;
-
-    for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
-    {
-      StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
-
-      if (mapping == null || mapping.length < 1)
-      {
-        continue;
-      }
-
-      int startPos = -1, lastPos = -1;
-      String lastChain = "";
-      for (int s = 0; s < sequence[pdbfnum].length; s++)
-      {
-        for (int sp, m = 0; m < mapping.length; m++)
-        {
-          final SequenceI seq = sequence[pdbfnum][s];
-          if (mapping[m].getSequence() == seq
-                  && (sp = al.findIndex(seq)) > -1)
-          {
-            SequenceI asp = al.getSequenceAt(sp);
-            for (int r = 0; r < asp.getLength(); r++)
-            {
-              // no mapping to gaps in sequence
-              if (Comparison.isGap(asp.getCharAt(r)))
-              {
-                continue;
-              }
-              int pos = mapping[m].getPDBResNum(asp.findPosition(r));
-
-              if (pos < 1 || pos == lastPos)
-              {
-                continue;
-              }
-
-              Color colour = sr.getResidueColour(seq, r, finder);
-
-              /*
-               * darker colour for hidden regions
-               */
-              if (!cs.isVisible(r))
-              {
-                colour = Color.GRAY;
-              }
-
-              final String chain = mapping[m].getChain();
-
-              /*
-               * Just keep incrementing the end position for this colour range
-               * _unless_ colour, PDB model or chain has changed, or there is a
-               * gap in the mapped residue sequence
-               */
-              final boolean newColour = !colour.equals(lastColour);
-              final boolean nonContig = lastPos + 1 != pos;
-              final boolean newChain = !chain.equals(lastChain);
-              if (newColour || nonContig || newChain)
-              {
-                if (startPos != -1)
-                {
-                  addAtomSpecRange(colourMap, lastColour, pdbfnum, startPos,
-                          lastPos, lastChain);
-                }
-                startPos = pos;
-              }
-              lastColour = colour;
-              lastPos = pos;
-              lastChain = chain;
-            }
-            // final colour range
-            if (lastColour != null)
-            {
-              addAtomSpecRange(colourMap, lastColour, pdbfnum, startPos,
-                      lastPos, lastChain);
-            }
-            // break;
-          }
-        }
-      }
-    }
-    return colourMap;
+    // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/color.html
+    String colourCode = getColourString(colour);
+    return "color " + colourCode + " " + atomSpec;
   }
 
   /**
-   * Helper method to add one contiguous range to the AtomSpec model for the given
-   * value (creating the model if necessary). As used by Jalview, {@code value} is
-   * <ul>
-   * <li>a colour, when building a 'colour structure by sequence' command</li>
-   * <li>a feature value, when building a 'set Chimera attributes from features'
-   * command</li>
-   * </ul>
+   * Returns a colour formatted suitable for use in viewer command syntax
    * 
-   * @param map
-   * @param value
-   * @param model
-   * @param startPos
-   * @param endPos
-   * @param chain
+   * @param colour
+   * @return
    */
-  protected static void addAtomSpecRange(Map<Object, AtomSpecModel> map,
-          Object value, int model, int startPos, int endPos, String chain)
+  protected String getColourString(Color colour)
   {
-    /*
-     * Get/initialize map of data for the colour
-     */
-    AtomSpecModel atomSpec = map.get(value);
-    if (atomSpec == null)
-    {
-      atomSpec = new AtomSpecModel();
-      map.put(value, atomSpec);
-    }
-
-    atomSpec.addRange(model, startPos, endPos, chain);
+    return ColorUtils.toTkCode(colour);
   }
 
   /**
    * Constructs and returns Chimera commands to set attributes on residues
-   * corresponding to features in Jalview. Attribute names are the Jalview
-   * feature type, with a "jv_" prefix.
+   * corresponding to features in Jalview. Attribute names are the Jalview feature
+   * type, with a "jv_" prefix.
    * 
    * @param ssm
    * @param files
@@ -325,20 +91,15 @@ public class ChimeraCommands
    * @param viewPanel
    * @return
    */
-  public static StructureMappingcommandSet getSetAttributeCommandsForFeatures(
+  @Override
+  public String[] setAttributesForFeatures(
           StructureSelectionManager ssm, String[] files, SequenceI[][] seqs,
           AlignmentViewPanel viewPanel)
   {
     Map<String, Map<Object, AtomSpecModel>> featureMap = buildFeaturesMap(
             ssm, files, seqs, viewPanel);
 
-    List<String> commands = buildSetAttributeCommands(featureMap);
-
-    StructureMappingcommandSet cs = new StructureMappingcommandSet(
-            ChimeraCommands.class, null,
-            commands.toArray(new String[commands.size()]));
-
-    return cs;
+    return setAttributes(featureMap);
   }
 
   /**
@@ -353,7 +114,7 @@ public class ChimeraCommands
    * @param viewPanel
    * @return
    */
-  protected static Map<String, Map<Object, AtomSpecModel>> buildFeaturesMap(
+  protected Map<String, Map<Object, AtomSpecModel>> buildFeaturesMap(
           StructureSelectionManager ssm, String[] files, SequenceI[][] seqs,
           AlignmentViewPanel viewPanel)
   {
@@ -393,6 +154,7 @@ public class ChimeraCommands
     AlignmentI alignment = viewPanel.getAlignment();
     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
     {
+      final int modelNumber = pdbfnum + getModelStartNo();
       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
 
       if (mapping == null || mapping.length < 1)
@@ -416,12 +178,12 @@ public class ChimeraCommands
             if (!visibleFeatures.isEmpty())
             {
               scanSequenceFeatures(visibleFeatures, structureMapping, seq,
-                      theMap, pdbfnum);
+                      theMap, modelNumber);
             }
             if (showLinkedFeatures)
             {
               scanComplementFeatures(complementRenderer, structureMapping,
-                      seq, theMap, pdbfnum);
+                      seq, theMap, modelNumber);
             }
           }
         }
@@ -574,14 +336,14 @@ public class ChimeraCommands
    * 
    * <pre>
    * <blockquote> setattr r <featureName> " " #modelnumber:range.chain 
-   * e.g. setattr r jv:chain <value> #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,...
+   * e.g. setattr r jv_chain &lt;value&gt; #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,...
    * </blockquote>
    * </pre>
    * 
    * @param featureMap
    * @return
    */
-  protected static List<String> buildSetAttributeCommands(
+  protected String[] setAttributes(
           Map<String, Map<Object, AtomSpecModel>> featureMap)
   {
     List<String> commands = new ArrayList<>();
@@ -603,17 +365,40 @@ public class ChimeraCommands
          * add a command to set the attribute on the mapped residues
          * Put values in single quotes, encoding any embedded single quotes
          */
-        StringBuilder sb = new StringBuilder(128);
+        AtomSpecModel atomSpecModel = values.get(value);
         String featureValue = value.toString();
         featureValue = featureValue.replaceAll("\\'", "&#39;");
-        sb.append("setattr r ").append(attributeName).append(" '")
-                .append(featureValue).append("' ");
-        sb.append(values.get(value).getAtomSpec());
-        commands.add(sb.toString());
+        String cmd = setAttribute(attributeName, featureValue,
+                atomSpecModel);
+        commands.add(cmd);
       }
     }
 
-    return commands;
+    return commands.toArray(new String[commands.size()]);
+  }
+
+  /**
+   * Returns a viewer command to set the given residue attribute value on
+   * residues specified by the AtomSpecModel, for example
+   * 
+   * <pre>
+   * setatr res jv_chain 'primary' #1:12-34,48-55.B
+   * </pre>
+   * 
+   * @param attributeName
+   * @param attributeValue
+   * @param atomSpecModel
+   * @return
+   */
+  protected String setAttribute(String attributeName,
+          String attributeValue,
+          AtomSpecModel atomSpecModel)
+  {
+    StringBuilder sb = new StringBuilder(128);
+    sb.append("setattr res ").append(attributeName).append(" '")
+            .append(attributeValue).append("' ");
+    sb.append(getAtomSpec(atomSpecModel, false));
+    return sb.toString();
   }
 
   /**
@@ -623,10 +408,7 @@ public class ChimeraCommands
    * 
    * @param featureType
    * @return
-   * 
-   *         <pre>
-   * &#64;see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/setattr.html
-   *         </pre>
+   * @see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/setattr.html
    */
   protected static String makeAttributeName(String featureType)
   {
@@ -652,4 +434,232 @@ public class ChimeraCommands
     return attName;
   }
 
+  @Override
+  public String colourByChain()
+  {
+    return CMD_COLOUR_BY_CHAIN;
+  }
+
+  @Override
+  public String colourByCharge()
+  {
+    return CMD_COLOUR_BY_CHARGE;
+  }
+
+  @Override
+  public String getResidueSpec(String residue)
+  {
+    return "::" + residue;
+  }
+
+  @Override
+  public String setBackgroundColour(Color col)
+  {
+    // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/set.html#bgcolor
+    return "set bgColor " + ColorUtils.toTkCode(col);
+  }
+
+  @Override
+  public String focusView()
+  {
+    // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/focus.html
+    return "focus";
+  }
+
+  @Override
+  public String showChains(List<String> toShow)
+  {
+    /*
+     * Construct a chimera command like
+     * 
+     * ~display #*;~ribbon #*;ribbon :.A,:.B
+     */
+    StringBuilder cmd = new StringBuilder(64);
+    boolean first = true;
+    for (String chain : toShow)
+    {
+      String[] tokens = chain.split(":");
+      if (tokens.length == 2)
+      {
+        String showChainCmd = tokens[0] + ":." + tokens[1];
+        if (!first)
+        {
+          cmd.append(",");
+        }
+        cmd.append(showChainCmd);
+        first = false;
+      }
+    }
+
+    /*
+     * could append ";focus" to this command to resize the display to fill the
+     * window, but it looks more helpful not to (easier to relate chains to the
+     * whole)
+     */
+    final String command = "~display #*; ~ribbon #*; ribbon :"
+            + cmd.toString();
+    return command;
+  }
+
+  @Override
+  public String superposeStructures(AtomSpecModel spec, AtomSpecModel ref)
+  {
+    /*
+     * Form Chimera match command to match spec to ref
+     * 
+     * match #1:1-30.B,81-100.B@CA #0:21-40.A,61-90.A@CA
+     * 
+     * @see
+     * https://www.cgl.ucsf.edu/chimera/docs/UsersGuide/midas/match.html
+     */
+    StringBuilder cmd = new StringBuilder();
+    String atomSpec = getAtomSpec(spec, true);
+    String refSpec = getAtomSpec(ref, true);
+    cmd.append("match ").append(atomSpec).append(" ").append(refSpec);
+
+    /*
+     * show superposed residues as ribbon, others as chain
+     */
+    // fixme this should precede the loop over all alignments/structures
+    cmd.append(";~display all; chain @CA|P");
+    cmd.append("; ribbon ");
+    cmd.append(atomSpec).append("|").append(refSpec).append("; focus");
+
+    return cmd.toString();
+  }
+
+  @Override
+  public String openCommandFile(String path)
+  {
+    // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/filetypes.html
+    return "open cmd:" + path;
+  }
+
+  @Override
+  public String saveSession(String filepath)
+  {
+    // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/save.html
+    return "save " + filepath;
+  }
+
+  /**
+   * Returns the range(s) modelled by {@code atomSpec} formatted as a Chimera
+   * atomspec string, e.g.
+   * 
+   * <pre>
+   * #0:15.A,28.A,54.A,70-72.A|#1:2.A,6.A,11.A,13-14.A
+   * </pre>
+   * 
+   * where
+   * <ul>
+   * <li>#0 is a model number</li>
+   * <li>15 or 70-72 is a residue number, or range of residue numbers</li>
+   * <li>.A is a chain identifier</li>
+   * <li>residue ranges are separated by comma</li>
+   * <li>atomspecs for distinct models are separated by | (or)</li>
+   * </ul>
+   * 
+   * <pre>
+   * 
+   * @param model
+   * @param alphaOnly
+   * @return
+   * @see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec.html
+   */
+  @Override
+  public String getAtomSpec(AtomSpecModel atomSpec, boolean alphaOnly)
+  {
+    StringBuilder sb = new StringBuilder(128);
+    boolean firstModel = true;
+    for (Integer model : atomSpec.getModels())
+    {
+      if (!firstModel)
+      {
+        sb.append("|");
+      }
+      firstModel = false;
+      appendModel(sb, model, atomSpec, alphaOnly);
+    }
+    return sb.toString();
+  }
+
+  /**
+   * A helper method to append an atomSpec string for atoms in the given model
+   * 
+   * @param sb
+   * @param model
+   * @param atomSpec
+   * @param alphaOnly
+   */
+  protected void appendModel(StringBuilder sb, Integer model,
+          AtomSpecModel atomSpec, boolean alphaOnly)
+  {
+    sb.append("#").append(model).append(":");
+
+    boolean firstPositionForModel = true;
+
+    for (String chain : atomSpec.getChains(model))
+    {
+      chain = " ".equals(chain) ? chain : chain.trim();
+
+      List<int[]> rangeList = atomSpec.getRanges(model, chain);
+
+      /*
+       * sort ranges into ascending start position order
+       */
+      Collections.sort(rangeList, IntRangeComparator.ASCENDING);
+
+      int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0];
+      int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1];
+
+      Iterator<int[]> iterator = rangeList.iterator();
+      while (iterator.hasNext())
+      {
+        int[] range = iterator.next();
+        if (range[0] <= end + 1)
+        {
+          /*
+           * range overlaps or is contiguous with the last one
+           * - so just extend the end position, and carry on
+           * (unless this is the last in the list)
+           */
+          end = Math.max(end, range[1]);
+        }
+        else
+        {
+          /*
+           * we have a break so append the last range
+           */
+          appendRange(sb, start, end, chain, firstPositionForModel, false);
+          firstPositionForModel = false;
+          start = range[0];
+          end = range[1];
+        }
+      }
+
+      /*
+       * and append the last range
+       */
+      if (!rangeList.isEmpty())
+      {
+        appendRange(sb, start, end, chain, firstPositionForModel, false);
+        firstPositionForModel = false;
+      }
+    }
+    if (alphaOnly)
+    {
+      /*
+       * restrict to alpha carbon, no alternative locations
+       * (needed to ensuring matching atom counts for superposition)
+       */
+      sb.append("@CA|P").append(NO_ALTLOCS);
+    }
+  }
+
+  @Override
+  public String showBackbone()
+  {
+    return "~display all;chain @CA|P";
+  }
+
 }
index a0d74bc..40b0ff0 100644 (file)
@@ -114,17 +114,25 @@ public class ChimeraListener extends AbstractRequestHandler
   {
     // dumpRequest(request);
     String message = request.getParameter(CHIMERA_NOTIFICATION);
-    if (SELECTION_CHANGED.equals(message))
+    if (message == null)
     {
-      this.chimeraBinding.highlightChimeraSelection();
+      message = request.getParameter("chimerax_notification");
     }
-    else if (message != null && message.startsWith(MODEL_CHANGED))
+    if (message != null)
     {
-      processModelChanged(message.substring(MODEL_CHANGED.length()));
-    }
-    else
-    {
-      System.err.println("Unexpected chimeraNotification: " + message);
+      if (message.startsWith("SelectionChanged"))
+      {
+        this.chimeraBinding.highlightChimeraSelection();
+      }
+      else if (message.startsWith(MODEL_CHANGED))
+      {
+        System.err.println(message);
+        processModelChanged(message.substring(MODEL_CHANGED.length()));
+      }
+      else
+      {
+        System.err.println("Unexpected chimeraNotification: " + message);
+      }
     }
   }
 
diff --git a/src/jalview/ext/rbvi/chimera/ChimeraXCommands.java b/src/jalview/ext/rbvi/chimera/ChimeraXCommands.java
new file mode 100644 (file)
index 0000000..5eba203
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+ * 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.ext.rbvi.chimera;
+
+import jalview.structure.AtomSpecModel;
+import jalview.util.ColorUtils;
+import jalview.util.IntRangeComparator;
+
+import java.awt.Color;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Routines for generating ChimeraX commands for Jalview/ChimeraX binding
+ */
+public class ChimeraXCommands extends ChimeraCommands
+{
+  private static final String CMD_COLOUR_BY_CHARGE = "color white;color :ASP,GLU red;color :LYS,ARG blue;color :CYS yellow";
+
+  @Override
+  public String colourByCharge()
+  {
+    return CMD_COLOUR_BY_CHARGE;
+  }
+
+  @Override
+  public String getResidueSpec(String residue)
+  {
+    return ":" + residue;
+  }
+
+  @Override
+  public String setBackgroundColour(Color col)
+  {
+    // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/set.html
+    return "set bgColor " + ColorUtils.toTkCode(col);
+  }
+
+  @Override
+  public String getColourCommand(String atomSpec, Color colour)
+  {
+    // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/color.html
+    String colourCode = getColourString(colour);
+
+    return "color " + atomSpec + " " + colourCode;
+  }
+
+  @Override
+  public String focusView()
+  {
+    // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/view.html
+    return "view";
+  }
+
+  /**
+   * {@inheritDoc}
+   * 
+   * @return
+   */
+  @Override
+  public int getModelStartNo()
+  {
+    return 1;
+  }
+
+  /**
+   * 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
+   * </pre>
+   * 
+   * @param attributeName
+   * @param attributeValue
+   * @param atomSpecModel
+   * @return
+   */
+  @Override
+  protected String setAttribute(String attributeName,
+          String attributeValue, AtomSpecModel atomSpecModel)
+  {
+    StringBuilder sb = new StringBuilder(128);
+    sb.append("setattr ").append(getAtomSpec(atomSpecModel, false));
+    sb.append(" res ").append(attributeName).append(" '")
+            .append(attributeValue).append("'");
+    sb.append(" create true");
+    return sb.toString();
+  }
+
+  @Override
+  public String openCommandFile(String path)
+  {
+    // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/open.html
+    return "open " + path;
+  }
+
+  @Override
+  public String saveSession(String filepath)
+  {
+    // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/save.html
+    return "save session " + filepath;
+  }
+
+  /**
+   * 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
+   * 
+   * @return
+   */
+  @Override
+  public String getAtomSpec(AtomSpecModel atomSpec, boolean alphaOnly)
+  {
+    StringBuilder sb = new StringBuilder(128);
+    boolean firstModel = true;
+    for (Integer model : atomSpec.getModels())
+    {
+      if (!firstModel)
+      {
+        sb.append("|");
+      }
+      firstModel = false;
+      appendModel(sb, model, atomSpec);
+      if (alphaOnly)
+      {
+        sb.append("@CA|P");
+      }
+      // todo: is there ChimeraX syntax to exclude altlocs?
+    }
+    return sb.toString();
+  }
+
+  /**
+   * A helper method to append an atomSpec string for atoms in the given model
+   * 
+   * @param sb
+   * @param model
+   * @param atomSpec
+   */
+  protected void appendModel(StringBuilder sb, Integer model,
+          AtomSpecModel atomSpec)
+  {
+    sb.append("#").append(model);
+
+    for (String chain : atomSpec.getChains(model))
+    {
+      boolean firstPositionForChain = true;
+      sb.append("/").append(chain.trim()).append(":");
+      List<int[]> rangeList = atomSpec.getRanges(model, chain);
+
+      /*
+       * sort ranges into ascending start position order
+       */
+      Collections.sort(rangeList, IntRangeComparator.ASCENDING);
+
+      int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0];
+      int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1];
+
+      Iterator<int[]> iterator = rangeList.iterator();
+      while (iterator.hasNext())
+      {
+        int[] range = iterator.next();
+        if (range[0] <= end + 1)
+        {
+          /*
+           * range overlaps or is contiguous with the last one
+           * - so just extend the end position, and carry on
+           * (unless this is the last in the list)
+           */
+          end = Math.max(end, range[1]);
+        }
+        else
+        {
+          /*
+           * we have a break so append the last range
+           */
+          appendRange(sb, start, end, chain, firstPositionForChain, true);
+          start = range[0];
+          end = range[1];
+          firstPositionForChain = false;
+        }
+      }
+
+      /*
+       * and append the last range
+       */
+      if (!rangeList.isEmpty())
+      {
+        appendRange(sb, start, end, chain, firstPositionForChain, true);
+      }
+      firstPositionForChain = false;
+    }
+  }
+
+  @Override
+  public String showBackbone()
+  {
+    return "~display all;show @CA|P pbonds";
+  }
+
+  @Override
+  public String superposeStructures(AtomSpecModel spec, AtomSpecModel ref)
+  {
+    /*
+     * Form ChimeraX match command to match spec to ref
+     * 
+     * match #1/A:2-94 toAtoms #2/A:1-93
+     * 
+     * @see
+     * https://www.cgl.ucsf.edu/chimera/docs/UsersGuide/midas/match.html
+     */
+    StringBuilder cmd = new StringBuilder();
+    String atomSpec = getAtomSpec(spec, true);
+    String refSpec = getAtomSpec(ref, true);
+    cmd.append("align ").append(atomSpec).append(" toAtoms ")
+            .append(refSpec);
+
+    /*
+     * show superposed residues as ribbon, others as chain
+     */
+    cmd.append("; ribbon ");
+    cmd.append(getAtomSpec(spec, false)).append("|");
+    cmd.append(getAtomSpec(ref, false)).append("; view");
+
+    return cmd.toString();
+  }
+
+}
index 00446f2..ae34bd0 100644 (file)
@@ -21,7 +21,6 @@
 package jalview.ext.rbvi.chimera;
 
 import jalview.api.AlignmentViewPanel;
-import jalview.api.SequenceRenderer;
 import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
@@ -31,17 +30,15 @@ import jalview.datamodel.SearchResultMatchI;
 import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
+import jalview.gui.StructureViewer.ViewerType;
 import jalview.httpserver.AbstractRequestHandler;
 import jalview.io.DataSourceType;
-import jalview.schemes.ColourSchemeI;
-import jalview.schemes.ResidueProperties;
 import jalview.structure.AtomSpec;
-import jalview.structure.StructureMappingcommandSet;
+import jalview.structure.StructureCommandsI.SuperposeData;
 import jalview.structure.StructureSelectionManager;
 import jalview.structures.models.AAStructureBindingModel;
 import jalview.util.MessageManager;
 
-import java.awt.Color;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -50,7 +47,7 @@ import java.net.BindException;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collections;
-import java.util.Hashtable;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -67,23 +64,16 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   // Chimera clause to exclude alternate locations in atom selection
   private static final String NO_ALTLOCS = "&~@.B-Z&~@.2-9";
 
-  private static final String COLOURING_CHIMERA = MessageManager
-          .getString("status.colouring_chimera");
-
   private static final boolean debug = false;
 
   private static final String PHOSPHORUS = "P";
 
   private static final String ALPHACARBON = "CA";
 
-  private List<String> chainNames = new ArrayList<String>();
-
-  private Hashtable<String, String> chainFile = new Hashtable<String, String>();
-
   /*
    * Object through which we talk to Chimera
    */
-  private ChimeraManager viewer;
+  private ChimeraManager chimeraManager;
 
   /*
    * Object which listens to Chimera notifications
@@ -91,39 +81,19 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   private AbstractRequestHandler chimeraListener;
 
   /*
-   * set if chimera state is being restored from some source - instructs binding
-   * not to apply default display style when structure set is updated for first
-   * time.
-   */
-  private boolean loadingFromArchive = false;
-
-  /*
-   * flag to indicate if the Chimera viewer should ignore sequence colouring
-   * events from the structure manager because the GUI is still setting up
-   */
-  private boolean loadingFinished = true;
-
-  /*
    * Map of ChimeraModel objects keyed by PDB full local file name
    */
-  private Map<String, List<ChimeraModel>> chimeraMaps = new LinkedHashMap<String, List<ChimeraModel>>();
+  protected Map<String, List<ChimeraModel>> chimeraMaps = new LinkedHashMap<>();
 
   String lastHighlightCommand;
 
-  /*
-   * incremented every time a load notification is successfully handled -
-   * lightweight mechanism for other threads to detect when they can start
-   * referring to new structures.
-   */
-  private long loadNotifiesHandled = 0;
-
   private Thread chimeraMonitor;
 
   /**
    * Open a PDB structure file in Chimera and set up mappings from Jalview.
    * 
-   * We check if the PDB model id is already loaded in Chimera, if so don't
-   * reopen it. This is the case if Chimera has opened a saved session file.
+   * We check if the PDB model id is already loaded in Chimera, if so don't reopen
+   * it. This is the case if Chimera has opened a saved session file.
    * 
    * @param pe
    * @return
@@ -133,8 +103,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     String file = pe.getFile();
     try
     {
-      List<ChimeraModel> modelsToMap = new ArrayList<ChimeraModel>();
-      List<ChimeraModel> oldList = viewer.getModelList();
+      List<ChimeraModel> modelsToMap = new ArrayList<>();
+      List<ChimeraModel> oldList = chimeraManager.getModelList();
       boolean alreadyOpen = false;
 
       /*
@@ -155,16 +125,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
        */
       if (!alreadyOpen)
       {
-        viewer.openModel(file, pe.getId(), ModelType.PDB_MODEL);
-        List<ChimeraModel> newList = viewer.getModelList();
-        // JAL-1728 newList.removeAll(oldList) does not work
-        for (ChimeraModel cm : newList)
-        {
-          if (cm.getModelName().equals(pe.getId()))
-          {
-            modelsToMap.add(cm);
-          }
-        }
+        chimeraManager.openModel(file, pe.getId(), ModelType.PDB_MODEL);
+        addChimeraModel(pe, modelsToMap);
       }
 
       chimeraMaps.put(file, modelsToMap);
@@ -184,6 +146,31 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   }
 
   /**
+   * Adds the ChimeraModel corresponding to the given PDBEntry, based on model
+   * name matching PDB id
+   * 
+   * @param pe
+   * @param modelsToMap
+   */
+  protected void addChimeraModel(PDBEntry pe,
+          List<ChimeraModel> modelsToMap)
+  {
+    /*
+     * Chimera: query for actual models and find the one with
+     * matching model name - already set in viewer.openModel()
+     */
+    List<ChimeraModel> newList = chimeraManager.getModelList();
+    // JAL-1728 newList.removeAll(oldList) does not work
+    for (ChimeraModel cm : newList)
+    {
+      if (cm.getModelName().equals(pe.getId()))
+      {
+        modelsToMap.add(cm);
+      }
+    }
+  }
+
+  /**
    * Constructor
    * 
    * @param ssm
@@ -196,17 +183,25 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
           DataSourceType protocol)
   {
     super(ssm, pdbentry, sequenceIs, protocol);
-    viewer = new ChimeraManager(new StructureManager(true));
+    chimeraManager = new ChimeraManager(new StructureManager(true));
+    chimeraManager.setChimeraX(ViewerType.CHIMERAX.equals(getViewerType()));
+    setStructureCommands(new ChimeraCommands());
+  }
+
+  @Override
+  protected ViewerType getViewerType()
+  {
+    return ViewerType.CHIMERA;
   }
 
   /**
-   * Starts a thread that waits for the Chimera process to finish, so that we
-   * can then close the associated resources. This avoids leaving orphaned
-   * Chimera viewer panels in Jalview if the user closes Chimera.
+   * Starts a thread that waits for the Chimera process to finish, so that we can
+   * then close the associated resources. This avoids leaving orphaned Chimera
+   * viewer panels in Jalview if the user closes Chimera.
    */
   protected void startChimeraProcessMonitor()
   {
-    final Process p = viewer.getChimeraProcess();
+    final Process p = chimeraManager.getChimeraProcess();
     chimeraMonitor = new Thread(new Runnable()
     {
 
@@ -231,15 +226,15 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   }
 
   /**
-   * Start a dedicated HttpServer to listen for Chimera notifications, and tell
-   * it to start listening
+   * Start a dedicated HttpServer to listen for Chimera notifications, and tell it
+   * to start listening
    */
   public void startChimeraListener()
   {
     try
     {
       chimeraListener = new ChimeraListener(this);
-      viewer.startListening(chimeraListener.getUri());
+      chimeraManager.startListening(chimeraListener.getUri());
     } catch (BindException e)
     {
       System.err.println(
@@ -248,43 +243,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   }
 
   /**
-   * Tells Chimera to display only the specified chains
-   * 
-   * @param toshow
-   */
-  public void showChains(List<String> toshow)
-  {
-    /*
-     * Construct a chimera command like
-     * 
-     * ~display #*;~ribbon #*;ribbon :.A,:.B
-     */
-    StringBuilder cmd = new StringBuilder(64);
-    boolean first = true;
-    for (String chain : toshow)
-    {
-      int modelNumber = getModelNoForChain(chain);
-      String showChainCmd = modelNumber == -1 ? ""
-              : modelNumber + ":." + chain.split(":")[1];
-      if (!first)
-      {
-        cmd.append(",");
-      }
-      cmd.append(showChainCmd);
-      first = false;
-    }
-
-    /*
-     * could append ";focus" to this command to resize the display to fill the
-     * window, but it looks more helpful not to (easier to relate chains to the
-     * whole)
-     */
-    final String command = "~display #*; ~ribbon #*; ribbon :"
-            + cmd.toString();
-    sendChimeraCommand(command, false);
-  }
-
-  /**
    * Close down the Jalview viewer and listener, and (optionally) the associated
    * Chimera window.
    */
@@ -293,14 +251,14 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     getSsm().removeStructureViewerListener(this, this.getStructureFiles());
     if (closeChimera)
     {
-      viewer.exitChimera();
+      chimeraManager.exitChimera();
     }
     if (this.chimeraListener != null)
     {
       chimeraListener.shutdown();
       chimeraListener = null;
     }
-    viewer = null;
+    chimeraManager = null;
 
     if (chimeraMonitor != null)
     {
@@ -309,249 +267,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     releaseUIResources();
   }
 
-  @Override
-  public void colourByChain()
-  {
-    colourBySequence = false;
-    sendAsynchronousCommand("rainbow chain", COLOURING_CHIMERA);
-  }
-
-  /**
-   * Constructs and sends a Chimera command to colour by charge
-   * <ul>
-   * <li>Aspartic acid and Glutamic acid (negative charge) red</li>
-   * <li>Lysine and Arginine (positive charge) blue</li>
-   * <li>Cysteine - yellow</li>
-   * <li>all others - white</li>
-   * </ul>
-   */
-  @Override
-  public void colourByCharge()
-  {
-    colourBySequence = false;
-    String command = "color white;color red ::ASP;color red ::GLU;color blue ::LYS;color blue ::ARG;color yellow ::CYS";
-    sendAsynchronousCommand(command, COLOURING_CHIMERA);
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public String superposeStructures(AlignmentI[] _alignment,
-          int[] _refStructure, HiddenColumns[] _hiddenCols)
-  {
-    StringBuilder allComs = new StringBuilder(128);
-    String[] files = getStructureFiles();
-
-    if (!waitForFileLoad(files))
-    {
-      return null;
-    }
-
-    refreshPdbEntries();
-    StringBuilder selectioncom = new StringBuilder(256);
-    for (int a = 0; a < _alignment.length; a++)
-    {
-      int refStructure = _refStructure[a];
-      AlignmentI alignment = _alignment[a];
-      HiddenColumns hiddenCols = _hiddenCols[a];
-
-      if (refStructure >= files.length)
-      {
-        System.err.println("Ignoring invalid reference structure value "
-                + refStructure);
-        refStructure = -1;
-      }
-
-      /*
-       * 'matched' bit i will be set for visible alignment columns i where
-       * all sequences have a residue with a mapping to the PDB structure
-       */
-      BitSet matched = new BitSet();
-      for (int m = 0; m < alignment.getWidth(); m++)
-      {
-        if (hiddenCols == null || hiddenCols.isVisible(m))
-        {
-          matched.set(m);
-        }
-      }
-
-      SuperposeData[] structures = new SuperposeData[files.length];
-      for (int f = 0; f < files.length; f++)
-      {
-        structures[f] = new SuperposeData(alignment.getWidth());
-      }
-
-      /*
-       * Calculate the superposable alignment columns ('matched'), and the
-       * corresponding structure residue positions (structures.pdbResNo)
-       */
-      int candidateRefStructure = findSuperposableResidues(alignment,
-              matched, structures);
-      if (refStructure < 0)
-      {
-        /*
-         * If no reference structure was specified, pick the first one that has
-         * a mapping in the alignment
-         */
-        refStructure = candidateRefStructure;
-      }
-
-      int nmatched = matched.cardinality();
-      if (nmatched < 4)
-      {
-        return MessageManager.formatMessage("label.insufficient_residues",
-                nmatched);
-      }
-
-      /*
-       * Generate select statements to select regions to superimpose structures
-       */
-      String[] selcom = new String[files.length];
-      for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
-      {
-        String chainCd = "." + structures[pdbfnum].chain;
-        int lpos = -1;
-        boolean run = false;
-        StringBuilder molsel = new StringBuilder();
-
-        int nextColumnMatch = matched.nextSetBit(0);
-        while (nextColumnMatch != -1)
-        {
-          int pdbResNum = structures[pdbfnum].pdbResNo[nextColumnMatch];
-          if (lpos != pdbResNum - 1)
-          {
-            /*
-             * discontiguous - append last residue now
-             */
-            if (lpos != -1)
-            {
-              molsel.append(String.valueOf(lpos));
-              molsel.append(chainCd);
-              molsel.append(",");
-            }
-            run = false;
-          }
-          else
-          {
-            /*
-             * extending a contiguous run
-             */
-            if (!run)
-            {
-              /*
-               * start the range selection
-               */
-              molsel.append(String.valueOf(lpos));
-              molsel.append("-");
-            }
-            run = true;
-          }
-          lpos = pdbResNum;
-          nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1);
-        }
-
-        /*
-         * and terminate final selection
-         */
-        if (lpos != -1)
-        {
-          molsel.append(String.valueOf(lpos));
-          molsel.append(chainCd);
-        }
-        if (molsel.length() > 1)
-        {
-          selcom[pdbfnum] = molsel.toString();
-          selectioncom.append("#").append(String.valueOf(pdbfnum))
-                  .append(":");
-          selectioncom.append(selcom[pdbfnum]);
-          selectioncom.append(" ");
-          if (pdbfnum < files.length - 1)
-          {
-            selectioncom.append("| ");
-          }
-        }
-        else
-        {
-          selcom[pdbfnum] = null;
-        }
-      }
-
-      StringBuilder command = new StringBuilder(256);
-      for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
-      {
-        if (pdbfnum == refStructure || selcom[pdbfnum] == null
-                || selcom[refStructure] == null)
-        {
-          continue;
-        }
-        if (command.length() > 0)
-        {
-          command.append(";");
-        }
-
-        /*
-         * Form Chimera match command, from the 'new' structure to the
-         * 'reference' structure e.g. (50 residues, chain B/A, alphacarbons):
-         * 
-         * match #1:1-30.B,81-100.B@CA #0:21-40.A,61-90.A@CA
-         * 
-         * @see
-         * https://www.cgl.ucsf.edu/chimera/docs/UsersGuide/midas/match.html
-         */
-        command.append("match ").append(getModelSpec(pdbfnum)).append(":");
-        command.append(selcom[pdbfnum]);
-        command.append("@").append(
-                structures[pdbfnum].isRna ? PHOSPHORUS : ALPHACARBON);
-        // JAL-1757 exclude alternate CA locations
-        command.append(NO_ALTLOCS);
-        command.append(" ").append(getModelSpec(refStructure)).append(":");
-        command.append(selcom[refStructure]);
-        command.append("@").append(
-                structures[refStructure].isRna ? PHOSPHORUS : ALPHACARBON);
-        command.append(NO_ALTLOCS);
-      }
-      if (selectioncom.length() > 0)
-      {
-        if (debug)
-        {
-          System.out.println("Select regions:\n" + selectioncom.toString());
-          System.out.println(
-                  "Superimpose command(s):\n" + command.toString());
-        }
-        allComs.append("~display all; chain @CA|P; ribbon ")
-                .append(selectioncom.toString())
-                .append(";" + command.toString());
-      }
-    }
-
-    String error = null;
-    if (selectioncom.length() > 0)
-    {
-      // TODO: visually distinguish regions that were superposed
-      if (selectioncom.substring(selectioncom.length() - 1).equals("|"))
-      {
-        selectioncom.setLength(selectioncom.length() - 1);
-      }
-      if (debug)
-      {
-        System.out.println("Select regions:\n" + selectioncom.toString());
-      }
-      allComs.append("; ~display all; chain @CA|P; ribbon ")
-              .append(selectioncom.toString()).append("; focus");
-      List<String> chimeraReplies = sendChimeraCommand(allComs.toString(),
-              true);
-      for (String reply : chimeraReplies)
-      {
-        if (reply.toLowerCase().contains("unequal numbers of atoms"))
-        {
-          error = reply;
-        }
-      }
-    }
-    return error;
-  }
-
   /**
    * Helper method to construct model spec in Chimera format:
    * <ul>
@@ -569,7 +284,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   {
     if (pdbfnum < 0 || pdbfnum >= getPdbCount())
     {
-      return "";
+      return "#" + pdbfnum; // temp hack for ChimeraX
     }
 
     /*
@@ -592,13 +307,12 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
    */
   public boolean launchChimera()
   {
-    if (viewer.isChimeraLaunched())
+    if (chimeraManager.isChimeraLaunched())
     {
       return true;
     }
 
-    boolean launched = viewer
-            .launchChimera(StructureManager.getChimeraPaths());
+    boolean launched = chimeraManager.launchChimera(getChimeraPaths());
     if (launched)
     {
       startChimeraProcessMonitor();
@@ -611,6 +325,16 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   }
 
   /**
+   * Returns a list of candidate paths to the Chimera program executable
+   * 
+   * @return
+   */
+  protected List<String> getChimeraPaths()
+  {
+    return StructureManager.getChimeraPaths(false);
+  }
+
+  /**
    * Answers true if the Chimera process is still running, false if ended or not
    * started.
    * 
@@ -618,103 +342,53 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
    */
   public boolean isChimeraRunning()
   {
-    return viewer.isChimeraLaunched();
+    return chimeraManager.isChimeraLaunched();
   }
 
   /**
    * Send a command to Chimera, and optionally log and return any responses.
-   * <p>
-   * Does nothing, and returns null, if the command is the same as the last one
-   * sent [why?].
    * 
    * @param command
    * @param getResponse
    */
-  public List<String> sendChimeraCommand(final String command,
+  @Override
+  public List<String> executeCommand(final String command,
           boolean getResponse)
   {
-    if (viewer == null)
+    if (chimeraManager == null || command == null)
     {
       // ? thread running after viewer shut down
       return null;
     }
     List<String> reply = null;
-    viewerCommandHistory(false);
-    if (true /*lastCommand == null || !lastCommand.equals(command)*/)
+    // trim command or it may never find a match in the replyLog!!
+    List<String> lastReply = chimeraManager
+            .sendChimeraCommand(command.trim(), getResponse);
+    if (getResponse)
     {
-      // trim command or it may never find a match in the replyLog!!
-      List<String> lastReply = viewer.sendChimeraCommand(command.trim(),
-              getResponse);
-      if (getResponse)
+      reply = lastReply;
+      if (debug)
       {
-        reply = lastReply;
-        if (debug)
-        {
-          log("Response from command ('" + command + "') was:\n"
-                  + lastReply);
-        }
+        log("Response from command ('" + command + "') was:\n" + lastReply);
       }
     }
-    viewerCommandHistory(true);
 
     return reply;
   }
 
   /**
-   * Send a Chimera command asynchronously in a new thread. If the progress
-   * message is not null, display this message while the command is executing.
-   * 
-   * @param command
-   * @param progressMsg
-   */
-  protected abstract void sendAsynchronousCommand(String command,
-          String progressMsg);
-
-  /**
-   * Sends a set of colour commands to the structure viewer
-   * 
-   * @param colourBySequenceCommands
-   */
-  @Override
-  protected void colourBySequence(
-          StructureMappingcommandSet[] colourBySequenceCommands)
-  {
-    for (StructureMappingcommandSet cpdbbyseq : colourBySequenceCommands)
-    {
-      for (String command : cpdbbyseq.commands)
-      {
-        sendAsynchronousCommand(command, COLOURING_CHIMERA);
-      }
-    }
-  }
-
-  /**
-   * @param files
-   * @param sr
-   * @param viewPanel
-   * @return
-   */
-  @Override
-  protected StructureMappingcommandSet[] getColourBySequenceCommands(
-          String[] files, SequenceRenderer sr, AlignmentViewPanel viewPanel)
-  {
-    return ChimeraCommands.getColourBySequenceCommand(getSsm(), files,
-            getSequence(), sr, viewPanel);
-  }
-
-  /**
    * @param command
    */
   protected void executeWhenReady(String command)
   {
     waitForChimera();
-    sendChimeraCommand(command, false);
+    executeCommand(command, false);
     waitForChimera();
   }
 
   private void waitForChimera()
   {
-    while (viewer != null && viewer.isBusy())
+    while (chimeraManager != null && chimeraManager.isBusy())
     {
       try
       {
@@ -725,29 +399,10 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     }
   }
 
-  // End StructureListener
-  // //////////////////////////
-
-  /**
-   * instruct the Jalview binding to update the pdbentries vector if necessary
-   * prior to matching the viewer's contents to the list of structure files
-   * Jalview knows about.
-   */
-  public abstract void refreshPdbEntries();
-
-  /**
-   * map between index of model filename returned from getPdbFile and the first
-   * index of models from this file in the viewer. Note - this is not trimmed -
-   * use getPdbFile to get number of unique models.
-   */
-  private int _modelFileNameMap[];
-
-  // ////////////////////////////////
-  // /StructureListener
   @Override
   public synchronized String[] getStructureFiles()
   {
-    if (viewer == null)
+    if (chimeraManager == null)
     {
       return new String[0];
     }
@@ -757,9 +412,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   }
 
   /**
-   * Construct and send a command to highlight zero, one or more atoms. We do
-   * this by sending an "rlabel" command to show the residue label at that
-   * position.
+   * Construct and send a command to highlight zero, one or more atoms. We do this
+   * by sending an "rlabel" command to show the residue label at that position.
    */
   @Override
   public void highlightAtoms(List<AtomSpec> atoms)
@@ -769,6 +423,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       return;
     }
 
+    boolean forChimeraX = chimeraManager.isChimeraX();
     StringBuilder cmd = new StringBuilder(128);
     boolean first = true;
     boolean found = false;
@@ -783,18 +438,26 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       {
         if (first)
         {
-          cmd.append("rlabel #").append(cms.get(0).getModelNumber())
-                  .append(":");
+          cmd.append(forChimeraX ? "label #" : "rlabel #");
         }
         else
         {
           cmd.append(",");
         }
         first = false;
-        cmd.append(pdbResNum);
-        if (!chain.equals(" "))
+        if (forChimeraX)
         {
-          cmd.append(".").append(chain);
+          cmd.append(cms.get(0).getModelNumber())
+                  .append("/").append(chain).append(":").append(pdbResNum);
+        }
+        else
+        {
+          cmd.append(cms.get(0).getModelNumber())
+                  .append(":").append(pdbResNum);
+          if (!chain.equals(" ") && !forChimeraX)
+          {
+            cmd.append(".").append(chain);
+          }
         }
         found = true;
       }
@@ -814,11 +477,11 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
      */
     if (lastHighlightCommand != null)
     {
-      viewer.sendChimeraCommand("~" + lastHighlightCommand, false);
+      chimeraManager.sendChimeraCommand("~" + lastHighlightCommand, false);
     }
     if (found)
     {
-      viewer.sendChimeraCommand(command, false);
+      chimeraManager.sendChimeraCommand(command, false);
     }
     this.lastHighlightCommand = command;
   }
@@ -831,7 +494,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     /*
      * Ask Chimera for its current selection
      */
-    List<String> selection = viewer.getSelectedResidueSpecs();
+    List<String> selection = chimeraManager.getSelectedResidueSpecs();
 
     /*
      * Parse model number, residue and chain for each selected position,
@@ -857,12 +520,13 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   protected List<AtomSpec> convertStructureResiduesToAlignment(
           List<String> structureSelection)
   {
-    List<AtomSpec> atomSpecs = new ArrayList<AtomSpec>();
+    boolean chimeraX = chimeraManager.isChimeraX();
+    List<AtomSpec> atomSpecs = new ArrayList<>();
     for (String atomSpec : structureSelection)
     {
       try
       {
-        AtomSpec spec = AtomSpec.fromChimeraAtomspec(atomSpec);
+        AtomSpec spec = AtomSpec.fromChimeraAtomspec(atomSpec, chimeraX);
         String pdbfilename = getPdbFileForModel(spec.getModelNumber());
         spec.setPdbFile(pdbfilename);
         atomSpecs.add(spec);
@@ -903,105 +567,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     System.err.println("## Chimera log: " + message);
   }
 
-  private void viewerCommandHistory(boolean enable)
-  {
-    // log("(Not yet implemented) History "
-    // + ((debug || enable) ? "on" : "off"));
-  }
-
-  public long getLoadNotifiesHandled()
-  {
-    return loadNotifiesHandled;
-  }
-
-  @Override
-  public void setJalviewColourScheme(ColourSchemeI cs)
-  {
-    colourBySequence = false;
-
-    if (cs == null)
-    {
-      return;
-    }
-
-    // Chimera expects RBG values in the range 0-1
-    final double normalise = 255D;
-    viewerCommandHistory(false);
-    StringBuilder command = new StringBuilder(128);
-
-    List<String> residueSet = ResidueProperties.getResidues(isNucleotide(),
-            false);
-    for (String resName : residueSet)
-    {
-      char res = resName.length() == 3
-              ? ResidueProperties.getSingleCharacterCode(resName)
-              : resName.charAt(0);
-      Color col = cs.findColour(res, 0, null, null, 0f);
-      command.append("color " + col.getRed() / normalise + ","
-              + col.getGreen() / normalise + "," + col.getBlue() / normalise
-              + " ::" + resName + ";");
-    }
-
-    sendAsynchronousCommand(command.toString(), COLOURING_CHIMERA);
-    viewerCommandHistory(true);
-  }
-
-  /**
-   * called when the binding thinks the UI needs to be refreshed after a Chimera
-   * state change. this could be because structures were loaded, or because an
-   * error has occurred.
-   */
-  public abstract void refreshGUI();
-
-  @Override
-  public void setLoadingFromArchive(boolean loadingFromArchive)
-  {
-    this.loadingFromArchive = loadingFromArchive;
-  }
-
-  /**
-   * 
-   * @return true if Chimeral is still restoring state or loading is still going
-   *         on (see setFinsihedLoadingFromArchive)
-   */
-  @Override
-  public boolean isLoadingFromArchive()
-  {
-    return loadingFromArchive && !loadingFinished;
-  }
-
-  /**
-   * modify flag which controls if sequence colouring events are honoured by the
-   * binding. Should be true for normal operation
-   * 
-   * @param finishedLoading
-   */
-  @Override
-  public void setFinishedLoadingFromArchive(boolean finishedLoading)
-  {
-    loadingFinished = finishedLoading;
-  }
-
-  /**
-   * Send the Chimera 'background solid <color>" command.
-   * 
-   * @see https
-   *      ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/background
-   *      .html
-   * @param col
-   */
-  @Override
-  public void setBackgroundColour(Color col)
-  {
-    viewerCommandHistory(false);
-    double normalise = 255D;
-    final String command = "background solid " + col.getRed() / normalise
-            + "," + col.getGreen() / normalise + ","
-            + col.getBlue() / normalise + ";";
-    viewer.sendChimeraCommand(command, false);
-    viewerCommandHistory(true);
-  }
-
   /**
    * Ask Chimera to save its session to the given file. Returns true if
    * successful, else false.
@@ -1013,8 +578,12 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   {
     if (isChimeraRunning())
     {
-      List<String> reply = viewer.sendChimeraCommand("save " + filepath,
-              true);
+      /*
+       * Chimera:  https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/save.html
+       * ChimeraX: https://www.cgl.ucsf.edu/chimerax/docs/user/commands/save.html
+       */
+      String command = getCommandGenerator().saveSession(filepath);
+      List<String> reply = chimeraManager.sendChimeraCommand(command, true);
       if (reply.contains("Session written"))
       {
         return true;
@@ -1030,39 +599,24 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
 
   /**
    * Ask Chimera to open a session file. Returns true if successful, else false.
-   * The filename must have a .py extension for this command to work.
+   * The filename must have a .py (Chimera) or .cxs (ChimeraX) extension for
+   * this command to work.
    * 
    * @param filepath
    * @return
    */
   public boolean openSession(String filepath)
   {
-    sendChimeraCommand("open " + filepath, true);
+    /*
+     * Chimera:  https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/open.html
+     * ChimeraX: https://www.cgl.ucsf.edu/chimerax/docs/user/commands/open.html
+     */
+    executeCommand("open " + filepath, true);
     // todo: test for failure - how?
     return true;
   }
 
   /**
-   * Returns a list of chains mapped in this viewer. Note this list is not
-   * currently scoped per structure.
-   * 
-   * @return
-   */
-  @Override
-  public List<String> getChainNames()
-  {
-    return chainNames;
-  }
-
-  /**
-   * Send a 'focus' command to Chimera to recentre the visible display
-   */
-  public void focusView()
-  {
-    sendChimeraCommand("focus", false);
-  }
-
-  /**
    * Send a 'show' command for all atoms in the currently selected columns
    * 
    * TODO: pull up to abstract structure viewer interface
@@ -1100,18 +654,14 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   public int sendFeaturesToViewer(AlignmentViewPanel avp)
   {
     // TODO refactor as required to pull up to an interface
-    AlignmentI alignment = avp.getAlignment();
-
     String[] files = getStructureFiles();
     if (files == null)
     {
       return 0;
     }
 
-    StructureMappingcommandSet commandSet = ChimeraCommands
-            .getSetAttributeCommandsForFeatures(getSsm(), files,
-                    getSequence(), avp);
-    String[] commands = commandSet.commands;
+    String[] commands = getCommandGenerator()
+            .setAttributesForFeatures(getSsm(), files, getSequence(), avp);
     if (commands.length > 10)
     {
       sendCommandsByFile(commands);
@@ -1127,9 +677,9 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   }
 
   /**
-   * Write commands to a temporary file, and send a command to Chimera to open
-   * the file as a commands script. For use when sending a large number of
-   * separate commands would overload the REST interface mechanism.
+   * Write commands to a temporary file, and send a command to Chimera to open the
+   * file as a commands script. For use when sending a large number of separate
+   * commands would overload the REST interface mechanism.
    * 
    * @param commands
    */
@@ -1137,7 +687,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   {
     try
     {
-      File tmp = File.createTempFile("chim", ".com");
+      File tmp = File.createTempFile("chim", getCommandFileExtension());
       tmp.deleteOnExit();
       PrintWriter out = new PrintWriter(new FileOutputStream(tmp));
       for (String command : commands)
@@ -1147,7 +697,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       out.flush();
       out.close();
       String path = tmp.getAbsolutePath();
-      sendAsynchronousCommand("open cmd:" + path, null);
+      String command = getCommandGenerator().openCommandFile(path);
+      sendAsynchronousCommand(command, null);
     } catch (IOException e)
     {
       System.err.println("Sending commands to Chimera via file failed with "
@@ -1156,6 +707,16 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   }
 
   /**
+   * Returns the file extension required for a file of commands to be read by
+   * the structure viewer
+   * @return
+   */
+  protected String getCommandFileExtension()
+  {
+    return ".com";
+  }
+
+  /**
    * Get Chimera residues which have the named attribute, find the mapped
    * positions in the Jalview sequence(s), and set as sequence features
    * 
@@ -1176,7 +737,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     // fails for 'average.bfactor' (which is bad):
 
     String cmd = "list residues attr '" + attName + "'";
-    List<String> residues = sendChimeraCommand(cmd, true);
+    List<String> residues = executeCommand(cmd, true);
 
     boolean featureAdded = createFeaturesForAttributes(attName, residues);
     if (featureAdded)
@@ -1205,6 +766,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   {
     boolean featureAdded = false;
     String featureGroup = getViewerFeatureGroup();
+    boolean chimeraX = chimeraManager.isChimeraX();
 
     for (String residue : residues)
     {
@@ -1228,7 +790,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
 
       try
       {
-        spec = AtomSpec.fromChimeraAtomspec(atomSpec);
+        spec = AtomSpec.fromChimeraAtomspec(atomSpec, chimeraX);
       } catch (IllegalArgumentException e)
       {
         System.err.println("Problem parsing atomspec " + atomSpec);
@@ -1289,23 +851,52 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     return CHIMERA_FEATURE_GROUP;
   }
 
-  public Hashtable<String, String> getChainFile()
+  @Override
+  public int getModelNoForFile(String pdbFile)
+  {
+    List<ChimeraModel> foundModels = chimeraMaps.get(pdbFile);
+    if (foundModels != null && !foundModels.isEmpty())
+    {
+      return foundModels.get(0).getModelNumber();
+    }
+    return -1;
+  }
+
+  /**
+   * Answers a (possibly empty) list of attribute names in Chimera[X], excluding
+   * any which were added from Jalview
+   * 
+   * @return
+   */
+  public List<String> getChimeraAttributes()
   {
-    return chainFile;
+    List<String> atts = chimeraManager.getAttrList();
+    Iterator<String> it = atts.iterator();
+    while (it.hasNext())
+    {
+      if (it.next().startsWith(ChimeraCommands.NAMESPACE_PREFIX))
+      {
+        /*
+         * attribute added from Jalview - exclude it
+         */
+        it.remove();
+      }
+    }
+    return atts;
   }
 
-  public List<ChimeraModel> getChimeraModelByChain(String chain)
+  /**
+   * Returns the file extension to use for a saved viewer session file
+   * 
+   * @return
+   */
+  public String getSessionFileExtension()
   {
-    return chimeraMaps.get(chainFile.get(chain));
+    return ".py";
   }
 
-  public int getModelNoForChain(String chain)
+  public String getHelpURL()
   {
-    List<ChimeraModel> foundModels = getChimeraModelByChain(chain);
-    if (foundModels != null && !foundModels.isEmpty())
-    {
-      return foundModels.get(0).getModelNumber();
-    }
-    return -1;
+    return "https://www.cgl.ucsf.edu/chimera/docs/UsersGuide";
   }
 }
index b7d8b3a..51c9e09 100644 (file)
@@ -398,7 +398,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
 
     addKeyListener();
 
-    final List<AlignmentPanel> selviews = new ArrayList<>();
+    final List<AlignmentViewPanel> selviews = new ArrayList<>();
     final List<AlignmentPanel> origview = new ArrayList<>();
     final String menuLabel = MessageManager
             .getString("label.copy_format_from");
index ea7fb6b..0768c00 100644 (file)
@@ -20,6 +20,7 @@
  */
 package jalview.gui;
 
+import jalview.api.AlignmentViewPanel;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.PDBEntry;
@@ -27,6 +28,7 @@ import jalview.datamodel.SequenceI;
 import jalview.gui.StructureViewer.ViewerType;
 import jalview.structures.models.AAStructureBindingModel;
 import jalview.util.BrowserLauncher;
+import jalview.util.ImageMaker;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
 import jalview.ws.dbsources.Pdb;
@@ -37,13 +39,10 @@ import java.awt.Dimension;
 import java.awt.Font;
 import java.awt.Graphics;
 import java.awt.Rectangle;
-import java.awt.event.ActionEvent;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Vector;
 
-import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JPanel;
 import javax.swing.JSplitPane;
 import javax.swing.SwingUtilities;
@@ -154,14 +153,6 @@ public class AppJmol extends StructureViewerBase
             .getString("label.let_jmol_manage_structure_colours"));
   }
 
-  IProgressIndicator progressBar = null;
-
-  @Override
-  protected IProgressIndicator getIProgressIndicator()
-  {
-    return progressBar;
-  }
-  
   /**
    * display a single PDB structure in a new Jmol view
    * 
@@ -173,7 +164,7 @@ public class AppJmol extends StructureViewerBase
   public AppJmol(PDBEntry pdbentry, SequenceI[] seq, String[] chains,
           final AlignmentPanel ap)
   {
-    progressBar = ap.alignFrame;
+    setProgressIndicator(ap.alignFrame);
 
     openNewJmol(ap, alignAddedStructures, new PDBEntry[] { pdbentry },
             new SequenceI[][]
@@ -184,7 +175,7 @@ public class AppJmol extends StructureViewerBase
           PDBEntry[] pdbentrys,
           SequenceI[][] seqs)
   {
-    progressBar = ap.alignFrame;
+    setProgressIndicator(ap.alignFrame);
     jmb = new AppJmolBinding(this, ap.getStructureSelectionManager(),
             pdbentrys, seqs, null);
     addAlignmentPanel(ap);
@@ -254,30 +245,12 @@ public class AppJmol extends StructureViewerBase
     {
       command = "";
     }
-    jmb.evalStateCommand(command);
-    jmb.evalStateCommand("set hoverDelay=0.1");
+    jmb.executeCommand(command, false);
+    jmb.executeCommand("set hoverDelay=0.1", false);
     jmb.setFinishedInit(true);
   }
 
   @Override
-  void showSelectedChains()
-  {
-    Vector<String> toshow = new Vector<>();
-    for (int i = 0; i < chainMenu.getItemCount(); i++)
-    {
-      if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
-      {
-        JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
-        if (item.isSelected())
-        {
-          toshow.addElement(item.getText());
-        }
-      }
-    }
-    jmb.centerViewer(toshow);
-  }
-
-  @Override
   public void closeViewer(boolean closeExternalViewer)
   {
     // Jmol does not use an external viewer
@@ -355,7 +328,7 @@ public class AppJmol extends StructureViewerBase
 
       try
       {
-        jmb.evalStateCommand(command);
+        jmb.executeCommand(command, false);
       } catch (OutOfMemoryError oomerror)
       {
         new OOMWarning("When trying to add structures to the Jmol viewer!",
@@ -396,7 +369,7 @@ public class AppJmol extends StructureViewerBase
     }
 
     // refresh the sequence colours for the new structure(s)
-    for (AlignmentPanel ap : _colourwith)
+    for (AlignmentViewPanel ap : _colourwith)
     {
       jmb.updateColours(ap);
     }
@@ -418,7 +391,7 @@ public class AppJmol extends StructureViewerBase
       @Override
       public void run()
       {
-        if (jmb.viewer.isScriptExecuting())
+        if (jmb.jmolViewer.isScriptExecuting())
         {
           SwingUtilities.invokeLater(this);
           try
@@ -431,7 +404,7 @@ public class AppJmol extends StructureViewerBase
         }
         else
         {
-          alignStructs_withAllAlignPanels();
+          alignStructsWithAllAlignPanels();
         }
       }
     });
@@ -469,12 +442,9 @@ public class AppJmol extends StructureViewerBase
           AlignmentI pdbseq = null;
           pdbid = jmb.getPdbEntry(pi).getId();
           long hdl = pdbid.hashCode() - System.currentTimeMillis();
-          if (progressBar != null)
-          {
-            progressBar.setProgressBar(MessageManager
-                    .formatMessage("status.fetching_pdb", new String[]
-                    { pdbid }), hdl);
-          }
+          setProgressMessage(MessageManager
+                  .formatMessage("status.fetching_pdb", new String[]
+                  { pdbid }), hdl);
           try
           {
             pdbseq = pdbclient.getSequenceRecords(pdbid);
@@ -487,12 +457,8 @@ public class AppJmol extends StructureViewerBase
             errormsgs.append("'").append(pdbid).append("'");
           } finally
           {
-            if (progressBar != null)
-            {
-              progressBar.setProgressBar(
-                      MessageManager.getString("label.state_completed"),
-                      hdl);
-            }
+            setProgressMessage(
+                    MessageManager.getString("label.state_completed"), hdl);
           }
           if (pdbseq != null)
           {
@@ -550,53 +516,52 @@ public class AppJmol extends StructureViewerBase
   }
 
   @Override
-  public void eps_actionPerformed(ActionEvent e)
+  public void eps_actionPerformed()
   {
-    makePDBImage(jalview.util.ImageMaker.TYPE.EPS);
+    makePDBImage(ImageMaker.TYPE.EPS);
   }
 
   @Override
-  public void png_actionPerformed(ActionEvent e)
+  public void png_actionPerformed()
   {
-    makePDBImage(jalview.util.ImageMaker.TYPE.PNG);
+    makePDBImage(ImageMaker.TYPE.PNG);
   }
 
-  void makePDBImage(jalview.util.ImageMaker.TYPE type)
+  void makePDBImage(ImageMaker.TYPE type)
   {
     int width = getWidth();
     int height = getHeight();
 
-    jalview.util.ImageMaker im;
+    ImageMaker im;
 
-    if (type == jalview.util.ImageMaker.TYPE.PNG)
+    if (type == ImageMaker.TYPE.PNG)
     {
-      im = new jalview.util.ImageMaker(this,
-              jalview.util.ImageMaker.TYPE.PNG, "Make PNG image from view",
+      im = new ImageMaker(this, ImageMaker.TYPE.PNG,
+              "Make PNG image from view",
               width, height, null, null, null, 0, false);
     }
-    else if (type == jalview.util.ImageMaker.TYPE.EPS)
+    else if (type == ImageMaker.TYPE.EPS)
     {
-      im = new jalview.util.ImageMaker(this,
-              jalview.util.ImageMaker.TYPE.EPS, "Make EPS file from view",
+      im = new ImageMaker(this, ImageMaker.TYPE.EPS,
+              "Make EPS file from view",
               width, height, null, this.getTitle(), null, 0, false);
     }
     else
     {
-
       im = new jalview.util.ImageMaker(this,
-              jalview.util.ImageMaker.TYPE.SVG, "Make SVG file from PCA",
+              ImageMaker.TYPE.SVG, "Make SVG file from PCA",
               width, height, null, this.getTitle(), null, 0, false);
     }
 
     if (im.getGraphics() != null)
     {
-      jmb.viewer.renderScreenImage(im.getGraphics(), width, height);
+      jmb.jmolViewer.renderScreenImage(im.getGraphics(), width, height);
       im.writeImage();
     }
   }
 
   @Override
-  public void showHelp_actionPerformed(ActionEvent actionEvent)
+  public void showHelp_actionPerformed()
   {
     try
     {
@@ -604,12 +569,13 @@ public class AppJmol extends StructureViewerBase
               .openURL("http://jmol.sourceforge.net/docs/JmolUserGuide/");
     } catch (Exception ex)
     {
+      System.err.println("Show Jmol help failed with: " + ex.getMessage());
     }
   }
 
+  @Override
   public void showConsole(boolean showConsole)
   {
-
     if (showConsole)
     {
       if (splitPane == null)
@@ -675,7 +641,7 @@ public class AppJmol extends StructureViewerBase
           }
         }
       }
-      else if (jmb == null || jmb.viewer == null || !jmb.isFinishedInit())
+      else if (jmb == null || jmb.jmolViewer == null || !jmb.isFinishedInit())
       {
         g.setColor(Color.black);
         g.fillRect(0, 0, currentSize.width, currentSize.height);
@@ -686,7 +652,7 @@ public class AppJmol extends StructureViewerBase
       }
       else
       {
-        jmb.viewer.renderScreenImage(g, currentSize.width,
+        jmb.jmolViewer.renderScreenImage(g, currentSize.width,
                 currentSize.height);
       }
     }
@@ -701,7 +667,7 @@ public class AppJmol extends StructureViewerBase
   @Override
   public String getStateInfo()
   {
-    return jmb == null ? null : jmb.viewer.getStateInfo();
+    return jmb == null ? null : jmb.jmolViewer.getStateInfo();
   }
 
   @Override
index 724cec1..db698ac 100644 (file)
@@ -32,26 +32,20 @@ import jalview.structure.StructureSelectionManager;
 import java.awt.Container;
 import java.util.Map;
 
+import javax.swing.JComponent;
+
 import org.jmol.api.JmolAppConsoleInterface;
 import org.jmol.java.BS;
 import org.openscience.jmol.app.jmolpanel.console.AppConsole;
 
 public class AppJmolBinding extends JalviewJmolBinding
 {
-  private AppJmol appJmolWindow;
-
   public AppJmolBinding(AppJmol appJmol, StructureSelectionManager sSm,
           PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
           DataSourceType protocol)
   {
     super(sSm, pdbentry, sequenceIs, protocol);
-    appJmolWindow = appJmol;
-  }
-
-  @Override
-  protected IProgressIndicator getIProgressIndicator()
-  {
-    return appJmolWindow.progressBar;
+    setViewer(appJmol);
   }
 
   @Override
@@ -102,28 +96,14 @@ public class AppJmolBinding extends JalviewJmolBinding
       @Override
       public void run()
       {
-        appJmolWindow.updateTitleAndMenus();
-        appJmolWindow.revalidate();
+        JalviewStructureDisplayI theViewer = getViewer();
+        theViewer.updateTitleAndMenus();
+        ((JComponent) theViewer).revalidate();
       }
     });
   }
 
   @Override
-  public void updateColours(Object source)
-  {
-    AlignmentPanel ap = (AlignmentPanel) source;
-    // ignore events from panels not used to colour this view
-    if (!appJmolWindow.isUsedforcolourby(ap))
-    {
-      return;
-    }
-    if (!isLoadingFromArchive())
-    {
-      colourBySequence(ap);
-    }
-  }
-
-  @Override
   public void notifyScriptTermination(String strStatus, int msWalltime)
   {
     // todo - script termination doesn't happen ?
@@ -147,35 +127,26 @@ public class AppJmolBinding extends JalviewJmolBinding
   @Override
   public void selectionChanged(BS arg0)
   {
-    // TODO Auto-generated method stub
-
-  }
-
-  @Override
-  public void refreshPdbEntries()
-  {
-    // TODO Auto-generated method stub
-
   }
 
   @Override
   public void showConsole(boolean b)
   {
-    appJmolWindow.showConsole(b);
+    getViewer().showConsole(b);
   }
 
   @Override
   protected JmolAppConsoleInterface createJmolConsole(
           Container consolePanel, String buttonsToShow)
   {
-    viewer.setJmolCallbackListener(this);
-    return new AppConsole(viewer, consolePanel, buttonsToShow);
+    jmolViewer.setJmolCallbackListener(this);
+    return new AppConsole(jmolViewer, consolePanel, buttonsToShow);
   }
 
   @Override
   protected void releaseUIResources()
   {
-    appJmolWindow = null;
+    setViewer(null);
     closeConsole();
   }
 
@@ -184,7 +155,7 @@ public class AppJmolBinding extends JalviewJmolBinding
   {
     if (svl instanceof SeqPanel)
     {
-      appJmolWindow.removeAlignmentPanel(((SeqPanel) svl).ap);
+      getViewer().removeAlignmentPanel(((SeqPanel) svl).ap);
     }
   }
 
@@ -194,25 +165,4 @@ public class AppJmolBinding extends JalviewJmolBinding
     // TODO Auto-generated method stub
     return null;
   }
-
-  @Override
-  public JalviewStructureDisplayI getViewer()
-  {
-    return appJmolWindow;
-  }
-
-  @Override
-  public jalview.api.FeatureRenderer getFeatureRenderer(
-          AlignmentViewPanel alignment)
-  {
-    AlignmentPanel ap = (alignment == null)
-            ? appJmolWindow.getAlignmentPanel()
-            : (AlignmentPanel) alignment;
-    if (ap.av.isShowSequenceFeatures())
-    {
-      return ap.av.getAlignPanel().getSeqPanel().seqCanvas.fr;
-    }
-
-    return null;
-  }
 }
index a7349b8..1a5e901 100644 (file)
  */
 package jalview.gui;
 
+import jalview.api.AlignmentViewPanel;
 import jalview.api.FeatureRenderer;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
-import jalview.ext.rbvi.chimera.ChimeraCommands;
 import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
 import jalview.gui.StructureViewer.ViewerType;
 import jalview.io.DataSourceType;
@@ -47,9 +47,7 @@ import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Random;
 
-import javax.swing.JCheckBoxMenuItem;
 import javax.swing.JInternalFrame;
 import javax.swing.JMenu;
 import javax.swing.JMenuItem;
@@ -66,8 +64,6 @@ public class ChimeraViewFrame extends StructureViewerBase
 {
   private JalviewChimeraBinding jmb;
 
-  private IProgressIndicator progressBar = null;
-
   /*
    * Path to Chimera session file. This is set when an open Jalview/Chimera
    * session is saved, or on restore from a Jalview project (if it holds the
@@ -75,8 +71,6 @@ public class ChimeraViewFrame extends StructureViewerBase
    */
   private String chimeraSessionFile = null;
 
-  private Random random = new Random();
-
   private int myWidth = 500;
 
   private int myHeight = 150;
@@ -138,34 +132,21 @@ public class ChimeraViewFrame extends StructureViewerBase
    */
   protected void buildAttributesMenu(JMenu attributesMenu)
   {
-    List<String> atts = jmb.sendChimeraCommand("list resattr", true);
-    if (atts == null)
-    {
-      return;
-    }
+    List<String> atts = jmb.getChimeraAttributes();
     attributesMenu.removeAll();
     Collections.sort(atts);
-    for (String att : atts)
+    for (String attName : atts)
     {
-      final String attName = att.split(" ")[1];
-
-      /*
-       * ignore 'jv_*' attributes, as these are Jalview features that have
-       * been transferred to residue attributes in Chimera!
-       */
-      if (!attName.startsWith(ChimeraCommands.NAMESPACE_PREFIX))
+      JMenuItem menuItem = new JMenuItem(attName);
+      menuItem.addActionListener(new ActionListener()
       {
-        JMenuItem menuItem = new JMenuItem(attName);
-        menuItem.addActionListener(new ActionListener()
+        @Override
+        public void actionPerformed(ActionEvent e)
         {
-          @Override
-          public void actionPerformed(ActionEvent e)
-          {
-            getChimeraAttributes(attName);
-          }
-        });
-        attributesMenu.add(menuItem);
-      }
+          getChimeraAttributes(attName);
+        }
+      });
+      attributesMenu.add(menuItem);
     }
   }
 
@@ -183,9 +164,9 @@ public class ChimeraViewFrame extends StructureViewerBase
   /**
    * Send a command to Chimera to create residue attributes for Jalview features
    * <p>
-   * The syntax is: setattr r <attName> <attValue> <atomSpec>
+   * The syntax is: setattr r &lt;attName&gt; &lt;attValue&gt; &lt;atomSpec&gt;
    * <p>
-   * For example: setattr r jv:chain "Ferredoxin-1, Chloroplastic" #0:94.A
+   * For example: setattr r jv_chain "Ferredoxin-1, Chloroplastic" #0:94.A
    */
   protected void sendFeaturesToChimera()
   {
@@ -217,9 +198,9 @@ public class ChimeraViewFrame extends StructureViewerBase
    */
   protected void createProgressBar()
   {
-    if (progressBar == null)
+    if (getProgressIndicator() == null)
     {
-      progressBar = new ProgressBar(statusPanel, statusBar);
+      setProgressIndicator(new ProgressBar(statusPanel, statusBar));
     }
   }
 
@@ -227,8 +208,7 @@ public class ChimeraViewFrame extends StructureViewerBase
           SequenceI[][] seqs)
   {
     createProgressBar();
-    jmb = new JalviewChimeraBindingModel(this,
-            ap.getStructureSelectionManager(), pdbentrys, seqs, null);
+    jmb = newBindingModel(ap, pdbentrys, seqs);
     addAlignmentPanel(ap);
     useAlignmentPanelForColourbyseq(ap);
 
@@ -256,6 +236,13 @@ public class ChimeraViewFrame extends StructureViewerBase
 
   }
 
+  protected JalviewChimeraBindingModel newBindingModel(AlignmentPanel ap,
+          PDBEntry[] pdbentrys, SequenceI[][] seqs)
+  {
+    return new JalviewChimeraBindingModel(this,
+            ap.getStructureSelectionManager(), pdbentrys, seqs, null);
+  }
+
   /**
    * Create a new viewer from saved session state data including Chimera session
    * file
@@ -357,27 +344,6 @@ public class ChimeraViewFrame extends StructureViewerBase
   }
 
   /**
-   * Show only the selected chain(s) in the viewer
-   */
-  @Override
-  void showSelectedChains()
-  {
-    List<String> toshow = new ArrayList<>();
-    for (int i = 0; i < chainMenu.getItemCount(); i++)
-    {
-      if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
-      {
-        JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
-        if (item.isSelected())
-        {
-          toshow.add(item.getText());
-        }
-      }
-    }
-    jmb.showChains(toshow);
-  }
-
-  /**
    * Close down this instance of Jalview's Chimera viewer, giving the user the
    * option to close the associated Chimera window (process). They may wish to
    * keep it open until they have had an opportunity to save any work.
@@ -549,7 +515,7 @@ public class ChimeraViewFrame extends StructureViewerBase
 
             pdb = jmb.getSsm().setMapping(jmb.getSequence()[pos],
                     jmb.getChains()[pos], pe.getFile(), protocol,
-                    progressBar);
+                    getProgressIndicator());
             stashFoundChains(pdb, pe.getFile());
 
           } catch (OutOfMemoryError oomerror)
@@ -584,7 +550,7 @@ public class ChimeraViewFrame extends StructureViewerBase
       }
 
       // refresh the sequence colours for the new structure(s)
-      for (AlignmentPanel ap : _colourwith)
+      for (AlignmentViewPanel ap : _colourwith)
       {
         jmb.updateColours(ap);
       }
@@ -596,7 +562,7 @@ public class ChimeraViewFrame extends StructureViewerBase
           @Override
           public void run()
           {
-            alignStructs_withAllAlignPanels();
+            alignStructsWithAllAlignPanels();
           }
         }).start();
       }
@@ -622,7 +588,7 @@ public class ChimeraViewFrame extends StructureViewerBase
       String chid = new String(
               pdb.getId() + ":" + pdb.getChains().elementAt(i).id);
       jmb.getChainNames().add(chid);
-      jmb.getChainFile().put(chid, file);
+      jmb.addChainFile(chid, file);
     }
   }
 
@@ -671,63 +637,31 @@ public class ChimeraViewFrame extends StructureViewerBase
     return filePath;
   }
 
-  /**
-   * Convenience method to update the progress bar if there is one. Be sure to
-   * call stopProgressBar with the returned handle to remove the message.
-   * 
-   * @param msg
-   * @param handle
-   */
-  public long startProgressBar(String msg)
-  {
-    // TODO would rather have startProgress/stopProgress as the
-    // IProgressIndicator interface
-    long tm = random.nextLong();
-    if (progressBar != null)
-    {
-      progressBar.setProgressBar(msg, tm);
-    }
-    return tm;
-  }
-
-  /**
-   * End the progress bar with the specified handle, leaving a message (if not
-   * null) on the status bar
-   * 
-   * @param msg
-   * @param handle
-   */
-  public void stopProgressBar(String msg, long handle)
-  {
-    if (progressBar != null)
-    {
-      progressBar.setProgressBar(msg, handle);
-    }
-  }
-
   @Override
-  public void eps_actionPerformed(ActionEvent e)
+  public void eps_actionPerformed()
   {
     throw new Error(MessageManager
             .getString("error.eps_generation_not_implemented"));
   }
 
   @Override
-  public void png_actionPerformed(ActionEvent e)
+  public void png_actionPerformed()
   {
     throw new Error(MessageManager
             .getString("error.png_generation_not_implemented"));
   }
 
   @Override
-  public void showHelp_actionPerformed(ActionEvent actionEvent)
+  public void showHelp_actionPerformed()
   {
     try
     {
-      BrowserLauncher
-              .openURL("https://www.cgl.ucsf.edu/chimera/docs/UsersGuide");
+      String url = jmb.getHelpURL();
+      BrowserLauncher.openURL(url);
     } catch (IOException ex)
     {
+      System.err
+              .println("Show Chimera help failed with: " + ex.getMessage());
     }
   }
 
@@ -752,7 +686,8 @@ public class ChimeraViewFrame extends StructureViewerBase
     {
       if (pathUsed == null)
       {
-        File tempFile = File.createTempFile("chimera", ".py");
+        String suffix = jmb.getSessionFileExtension();
+        File tempFile = File.createTempFile("chimera", suffix);
         tempFile.deleteOnExit();
         pathUsed = tempFile.getPath();
       }
@@ -824,26 +759,4 @@ public class ChimeraViewFrame extends StructureViewerBase
   {
     return "Chimera";
   }
-
-  /**
-   * Sends commands to align structures according to associated alignment(s).
-   * 
-   * @return
-   */
-  @Override
-  protected String alignStructs_withAllAlignPanels()
-  {
-    String reply = super.alignStructs_withAllAlignPanels();
-    if (reply != null)
-    {
-      statusBar.setText("Superposition failed: " + reply);
-    }
-    return reply;
-  }
-
-  @Override
-  protected IProgressIndicator getIProgressIndicator()
-  {
-    return progressBar;
-  }
 }
diff --git a/src/jalview/gui/ChimeraXViewFrame.java b/src/jalview/gui/ChimeraXViewFrame.java
new file mode 100644 (file)
index 0000000..b33ccd6
--- /dev/null
@@ -0,0 +1,50 @@
+package jalview.gui;
+
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.gui.StructureViewer.ViewerType;
+
+/**
+ * A class for the gui frame through which Jalview interacts with the ChimeraX
+ * structure viewer. Mostly the same as ChimeraViewFrame with a few overrides
+ * for the differences.
+ * 
+ * @author gmcarstairs
+ *
+ */
+public class ChimeraXViewFrame extends ChimeraViewFrame
+{
+
+  public ChimeraXViewFrame(PDBEntry pdb, SequenceI[] seqsForPdb,
+          String[] chains, AlignmentPanel ap)
+  {
+    super(pdb, seqsForPdb, chains, ap);
+  }
+
+  public ChimeraXViewFrame(PDBEntry[] pdbsForFile, boolean superposeAdded,
+          SequenceI[][] theSeqs, AlignmentPanel ap)
+  {
+    super(pdbsForFile, superposeAdded, theSeqs, ap);
+  }
+
+  @Override
+  public ViewerType getViewerType()
+  {
+    return ViewerType.CHIMERAX;
+  }
+
+  @Override
+  protected String getViewerName()
+  {
+    return "ChimeraX";
+  }
+
+  @Override
+  protected JalviewChimeraBindingModel newBindingModel(AlignmentPanel ap,
+          PDBEntry[] pdbentrys, SequenceI[][] seqs)
+  {
+    return new JalviewChimeraXBindingModel(this,
+            ap.getStructureSelectionManager(), pdbentrys, seqs, null);
+  }
+
+}
index 9d63c6a..49655a4 100644 (file)
@@ -27,33 +27,18 @@ import jalview.datamodel.SequenceI;
 import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
 import jalview.io.DataSourceType;
 import jalview.structure.StructureSelectionManager;
-import jalview.viewmodel.seqfeatures.FeatureRendererModel;
 
+import javax.swing.JComponent;
 import javax.swing.SwingUtilities;
 
 public class JalviewChimeraBindingModel extends JalviewChimeraBinding
 {
-  private ChimeraViewFrame cvf;
-
   public JalviewChimeraBindingModel(ChimeraViewFrame chimeraViewFrame,
           StructureSelectionManager ssm, PDBEntry[] pdbentry,
           SequenceI[][] sequenceIs, DataSourceType protocol)
   {
     super(ssm, pdbentry, sequenceIs, protocol);
-    cvf = chimeraViewFrame;
-  }
-
-  @Override
-  public FeatureRendererModel getFeatureRenderer(AlignmentViewPanel alignment)
-  {
-    AlignmentPanel ap = (alignment == null) ? cvf.getAlignmentPanel()
-            : (AlignmentPanel) alignment;
-    if (ap.av.isShowSequenceFeatures())
-    {
-      return ap.getSeqPanel().seqCanvas.fr;
-    }
-
-    return null;
+    setViewer(chimeraViewFrame);
   }
 
   @Override
@@ -71,74 +56,10 @@ public class JalviewChimeraBindingModel extends JalviewChimeraBinding
       @Override
       public void run()
       {
-        cvf.updateTitleAndMenus();
-        cvf.revalidate();
+        JalviewStructureDisplayI theViewer = getViewer();
+        theViewer.updateTitleAndMenus();
+        ((JComponent) theViewer).revalidate();
       }
     });
   }
-
-  @Override
-  public void updateColours(Object source)
-  {
-    AlignmentPanel ap = (AlignmentPanel) source;
-    // ignore events from panels not used to colour this view
-    if (!cvf.isUsedforcolourby(ap))
-    {
-      return;
-    }
-    if (!isLoadingFromArchive())
-    {
-      colourBySequence(ap);
-    }
-  }
-
-  @Override
-  public void releaseReferences(Object svl)
-  {
-  }
-
-  @Override
-  protected void releaseUIResources()
-  {
-  }
-
-  @Override
-  public void refreshPdbEntries()
-  {
-  }
-
-  /**
-   * Send an asynchronous command to Chimera, in a new thread, optionally with
-   * an 'in progress' message in a progress bar somewhere
-   */
-  @Override
-  protected void sendAsynchronousCommand(final String command,
-          final String progressMsg)
-  {
-    final long handle = progressMsg == null ? 0
-            : cvf.startProgressBar(progressMsg);
-    SwingUtilities.invokeLater(new Runnable()
-    {
-      @Override
-      public void run()
-      {
-        try
-        {
-          sendChimeraCommand(command, false);
-        } finally
-        {
-          if (progressMsg != null)
-          {
-            cvf.stopProgressBar(null, handle);
-          }
-        }
-      }
-    });
-  }
-
-  @Override
-  public JalviewStructureDisplayI getViewer()
-  {
-    return cvf;
-  }
 }
diff --git a/src/jalview/gui/JalviewChimeraXBindingModel.java b/src/jalview/gui/JalviewChimeraXBindingModel.java
new file mode 100644 (file)
index 0000000..3a6c89c
--- /dev/null
@@ -0,0 +1,82 @@
+package jalview.gui;
+
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
+import jalview.ext.rbvi.chimera.ChimeraXCommands;
+import jalview.gui.StructureViewer.ViewerType;
+import jalview.io.DataSourceType;
+import jalview.structure.StructureSelectionManager;
+
+import java.util.List;
+
+import ext.edu.ucsf.rbvi.strucviz2.ChimeraModel;
+import ext.edu.ucsf.rbvi.strucviz2.StructureManager;
+import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
+
+public class JalviewChimeraXBindingModel extends JalviewChimeraBindingModel
+{
+
+  public JalviewChimeraXBindingModel(ChimeraViewFrame chimeraViewFrame,
+          StructureSelectionManager ssm, PDBEntry[] pdbentry,
+          SequenceI[][] sequenceIs, DataSourceType protocol)
+  {
+    super(chimeraViewFrame, ssm, pdbentry, sequenceIs, protocol);
+    setStructureCommands(new ChimeraXCommands());
+  }
+
+  @Override
+  protected List<String> getChimeraPaths()
+  {
+    return StructureManager.getChimeraPaths(true);
+  }
+
+  @Override
+  protected void addChimeraModel(PDBEntry pe,
+          List<ChimeraModel> modelsToMap)
+  {
+    /*
+     * ChimeraX hack: force chimera model name to pdbId here
+     */
+    int modelNumber = chimeraMaps.size() + 1;
+    String command = "setattr #" + modelNumber + " models name "
+            + pe.getId();
+    executeCommand(command, false);
+    modelsToMap.add(new ChimeraModel(pe.getId(), ModelType.PDB_MODEL,
+            modelNumber, 0));
+  }
+
+  /**
+   * {@inheritDoc}
+   * 
+   * @return
+   */
+  @Override
+  protected String getCommandFileExtension()
+  {
+    return ".cxc";
+  }
+
+  /**
+   * Returns the file extension to use for a saved viewer session file
+   * 
+   * @return
+   */
+  @Override
+  public String getSessionFileExtension()
+  {
+    return ".cxs";
+  }
+
+  @Override
+  public String getHelpURL()
+  {
+    return "http://www.rbvi.ucsf.edu/chimerax/docs/user/index.html";
+  }
+
+  @Override
+  protected ViewerType getViewerType()
+  {
+    return ViewerType.CHIMERAX;
+  }
+
+}
index 1f61bae..3070ca1 100755 (executable)
@@ -105,6 +105,8 @@ public class Preferences extends GPreferences
 
   public static final String CHIMERA_PATH = "CHIMERA_PATH";
 
+  public static final String CHIMERAX_PATH = "CHIMERAX_PATH";
+
   public static final String SORT_ANNOTATIONS = "SORT_ANNOTATIONS";
 
   public static final String SHOW_AUTOCALC_ABOVE = "SHOW_AUTOCALC_ABOVE";
@@ -340,15 +342,30 @@ public class Preferences extends GPreferences
     addSecondaryStructure.setEnabled(structSelected);
     addTempFactor.setSelected(Cache.getDefault(ADD_TEMPFACT_ANN, false));
     addTempFactor.setEnabled(structSelected);
-    structViewer.setSelectedItem(
-            Cache.getDefault(STRUCTURE_DISPLAY, ViewerType.JMOL.name()));
-    chimeraPath.setText(Cache.getDefault(CHIMERA_PATH, ""));
+    String viewerType = Cache.getDefault(STRUCTURE_DISPLAY, ViewerType.JMOL.name());
+    structViewer.setSelectedItem(viewerType);
+    boolean isChimeraX = viewerType.equals(ViewerType.CHIMERAX.name());
+    if (viewerType.equals(ViewerType.JMOL.name()))
+    {
+      chimeraPath.setText("");
+    }
+    else
+    {
+      chimeraPath.setText(Cache
+              .getDefault(isChimeraX ? CHIMERAX_PATH : CHIMERA_PATH, ""));
+    }
     chimeraPath.addActionListener(new ActionListener()
     {
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        validateChimeraPath();
+        if (validateChimeraPath())
+        {
+          Cache.setProperty(structViewer.getSelectedItem()
+                  .equals(ViewerType.CHIMERAX.name())
+                  ? CHIMERAX_PATH
+                  : CHIMERA_PATH, chimeraPath.getText());
+        }
       }
     });
 
@@ -401,7 +418,7 @@ public class Preferences extends GPreferences
     doReset.addActionListener(onReset);
 
     // filter to display only custom urls
-    final RowFilter<TableModel, Object> customUrlFilter = new RowFilter<TableModel, Object>()
+    final RowFilter<TableModel, Object> customUrlFilter = new RowFilter<>()
     {
       @Override
       public boolean include(
@@ -682,7 +699,10 @@ public class Preferences extends GPreferences
             Boolean.toString(structFromPdb.isSelected()));
     Cache.applicationProperties.setProperty(STRUCTURE_DISPLAY,
             structViewer.getSelectedItem().toString());
-    Cache.setOrRemove(CHIMERA_PATH, chimeraPath.getText());
+    boolean isChimeraX = structViewer.getSelectedItem().toString()
+            .equals(ViewerType.CHIMERAX.name());
+    Cache.setOrRemove(isChimeraX ? CHIMERAX_PATH : CHIMERA_PATH,
+            chimeraPath.getText());
     Cache.applicationProperties.setProperty("MAP_WITH_SIFTS",
             Boolean.toString(siftsMapping.isSelected()));
     SiftsSettings.setMapWithSifts(siftsMapping.isSelected());
@@ -1208,22 +1228,32 @@ public class Preferences extends GPreferences
   }
 
   /**
-   * If Chimera is selected, check it can be found on default or user-specified
-   * path, if not show a warning/help dialog.
+   * If Chimera or ChimeraX 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)
   {
-    if (!selectedItem.equals(ViewerType.CHIMERA.name()))
+    if (selectedItem.equals(ViewerType.JMOL.name()))
     {
+      chimeraPath.setEnabled(false);
+      chimeraPathLabel.setEnabled(false);
       return;
     }
     boolean found = false;
+    chimeraPath.setEnabled(true);
+    chimeraPathLabel.setEnabled(true);
+    chimeraPathLabel.setText(MessageManager
+            .formatMessage("label.chimera_path", selectedItem));
 
     /*
-     * Try user-specified and standard paths for Chimera executable.
+     * Try user-specified and standard paths for Chimera executable
      */
-    List<String> paths = StructureManager.getChimeraPaths();
+    boolean isChimeraX = selectedItem.equals(ViewerType.CHIMERAX.name());
+    chimeraPath.setText(Cache
+            .getDefault(isChimeraX ? CHIMERAX_PATH : CHIMERA_PATH, ""));
+
+    List<String> paths = StructureManager.getChimeraPaths(isChimeraX);
     paths.add(0, chimeraPath.getText());
     for (String path : paths)
     {
index 0c8354b..79d3836 100644 (file)
@@ -56,7 +56,7 @@ public class StructureViewer
 
   public enum ViewerType
   {
-    JMOL, CHIMERA
+    JMOL, CHIMERA, CHIMERAX
   };
 
   /**
@@ -165,6 +165,11 @@ public class StructureViewer
       sview = new ChimeraViewFrame(pdbsForFile, superposeAdded, theSeqs,
               ap);
     }
+    else if (viewerType.equals(ViewerType.CHIMERAX))
+    {
+      sview = new ChimeraXViewFrame(pdbsForFile, superposeAdded, theSeqs,
+              ap);
+    }
     else
     {
       Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
@@ -305,6 +310,10 @@ public class StructureViewer
     {
       sview = new ChimeraViewFrame(pdb, seqsForPdb, null, ap);
     }
+    else if (viewerType.equals(ViewerType.CHIMERAX))
+    {
+      sview = new ChimeraXViewFrame(pdb, seqsForPdb, null, ap);
+    }
     else
     {
       Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
index 35a5475..6dd7d50 100644 (file)
@@ -22,9 +22,7 @@ package jalview.gui;
 
 import jalview.api.AlignmentViewPanel;
 import jalview.bin.Cache;
-import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.gui.StructureViewer.ViewerType;
@@ -53,6 +51,7 @@ import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Random;
 import java.util.Vector;
 
 import javax.swing.ButtonGroup;
@@ -90,13 +89,13 @@ public abstract class StructureViewerBase extends GStructureViewer
   /**
    * list of alignment panels to use for superposition
    */
-  protected Vector<AlignmentPanel> _alignwith = new Vector<>();
+  protected Vector<AlignmentViewPanel> _alignwith = new Vector<>();
 
   /**
    * list of alignment panels that are used for colouring structures by aligned
    * sequences
    */
-  protected Vector<AlignmentPanel> _colourwith = new Vector<>();
+  protected Vector<AlignmentViewPanel> _colourwith = new Vector<>();
 
   private String viewId = null;
 
@@ -121,6 +120,10 @@ public abstract class StructureViewerBase extends GStructureViewer
    */
   protected volatile boolean seqColoursApplied = false;
 
+  private IProgressIndicator progressBar = null;
+
+  private Random random = new Random();
+
   /**
    * Default constructor
    */
@@ -159,13 +162,14 @@ public abstract class StructureViewerBase extends GStructureViewer
     return _aps.contains(ap2.av.getSequenceSetId());
   }
 
-  public boolean isUsedforaligment(AlignmentPanel ap2)
+  public boolean isUsedforaligment(AlignmentViewPanel ap2)
   {
 
     return (_alignwith != null) && _alignwith.contains(ap2);
   }
 
-  public boolean isUsedforcolourby(AlignmentPanel ap2)
+  @Override
+  public boolean isUsedForColourBy(AlignmentViewPanel ap2)
   {
     return (_colourwith != null) && _colourwith.contains(ap2);
   }
@@ -215,6 +219,7 @@ public abstract class StructureViewerBase extends GStructureViewer
     }
   }
 
+  @Override
   public AlignmentPanel getAlignmentPanel()
   {
     return ap;
@@ -267,7 +272,8 @@ public abstract class StructureViewerBase extends GStructureViewer
    * 
    * @param nap
    */
-  public void removeAlignmentPanel(AlignmentPanel nap)
+  @Override
+  public void removeAlignmentPanel(AlignmentViewPanel nap)
   {
     try
     {
@@ -339,8 +345,6 @@ public abstract class StructureViewerBase extends GStructureViewer
 
   public abstract ViewerType getViewerType();
 
-  protected abstract IProgressIndicator getIProgressIndicator();
-
   /**
    * add a new structure (with associated sequences and chains) to this viewer,
    * retrieving it if necessary first.
@@ -449,7 +453,7 @@ public abstract class StructureViewerBase extends GStructureViewer
      * create the mappings
      */
     apanel.getStructureSelectionManager().setMapping(seq, chains,
-            pdbFilename, DataSourceType.FILE, getIProgressIndicator());
+            pdbFilename, DataSourceType.FILE, getProgressIndicator());
 
     /*
      * alert the FeatureRenderer to show new (PDB RESNUM) features
@@ -554,8 +558,6 @@ public abstract class StructureViewerBase extends GStructureViewer
     }
   }
 
-  abstract void showSelectedChains();
-
   /**
    * Action on selecting one of Jalview's registered colour schemes
    */
@@ -566,7 +568,7 @@ public abstract class StructureViewerBase extends GStructureViewer
     ColourSchemeI cs = ColourSchemes.getInstance()
             .getColourScheme(colourSchemeName, getAlignmentPanel().av, al,
                     null);
-    getBinding().setJalviewColourScheme(cs);
+    getBinding().colourByJalviewColourScheme(cs);
   }
 
   /**
@@ -600,7 +602,7 @@ public abstract class StructureViewerBase extends GStructureViewer
       @Override
       public void actionPerformed(ActionEvent actionEvent)
       {
-        viewerColour_actionPerformed(actionEvent);
+        viewerColour_actionPerformed();
       }
     });
     colourMenu.add(viewerColour);
@@ -616,7 +618,7 @@ public abstract class StructureViewerBase extends GStructureViewer
       @Override
       public void actionPerformed(ActionEvent actionEvent)
       {
-        background_actionPerformed(actionEvent);
+        background_actionPerformed();
       }
     });
     colourMenu.add(backGround);
@@ -647,7 +649,7 @@ public abstract class StructureViewerBase extends GStructureViewer
       @Override
       public void actionPerformed(ActionEvent actionEvent)
       {
-        seqColour_actionPerformed(actionEvent);
+        seqColour_actionPerformed();
       }
     });
 
@@ -659,7 +661,7 @@ public abstract class StructureViewerBase extends GStructureViewer
       @Override
       public void actionPerformed(ActionEvent actionEvent)
       {
-        chainColour_actionPerformed(actionEvent);
+        chainColour_actionPerformed();
       }
     });
 
@@ -671,7 +673,7 @@ public abstract class StructureViewerBase extends GStructureViewer
       @Override
       public void actionPerformed(ActionEvent actionEvent)
       {
-        chargeColour_actionPerformed(actionEvent);
+        chargeColour_actionPerformed();
       }
     });
 
@@ -703,7 +705,7 @@ public abstract class StructureViewerBase extends GStructureViewer
                 else
                 {
                   // update the Chimera display now.
-                  seqColour_actionPerformed(null);
+                  seqColour_actionPerformed();
                 }
               }
             });
@@ -747,24 +749,13 @@ public abstract class StructureViewerBase extends GStructureViewer
     buildColourMenu();
   }
 
-  @Override
-  public void setJalviewColourScheme(ColourSchemeI cs)
-  {
-    getBinding().setJalviewColourScheme(cs);
-  }
-
   /**
    * Sends commands to the structure viewer to superimpose structures based on
    * currently associated alignments. May optionally return an error message for
    * the operation.
    */
   @Override
-  protected String alignStructs_actionPerformed(ActionEvent actionEvent)
-  {
-    return alignStructs_withAllAlignPanels();
-  }
-
-  protected String alignStructs_withAllAlignPanels()
+  protected String alignStructsWithAllAlignPanels()
   {
     if (getAlignmentPanel() == null)
     {
@@ -779,19 +770,8 @@ public abstract class StructureViewerBase extends GStructureViewer
     String reply = null;
     try
     {
-      AlignmentI[] als = new Alignment[_alignwith.size()];
-      HiddenColumns[] alc = new HiddenColumns[_alignwith.size()];
-      int[] alm = new int[_alignwith.size()];
-      int a = 0;
-
-      for (AlignmentPanel alignPanel : _alignwith)
-      {
-        als[a] = alignPanel.av.getAlignment();
-        alm[a] = -1;
-        alc[a++] = alignPanel.av.getAlignment().getHiddenColumns();
-      }
-      reply = getBinding().superposeStructures(als, alm, alc);
-      if (reply != null)
+      reply = getBinding().superposeStructures(_alignwith);
+      if (reply != null && !reply.isEmpty())
       {
         String text = MessageManager
                 .formatMessage("error.superposition_failed", reply);
@@ -800,9 +780,9 @@ public abstract class StructureViewerBase extends GStructureViewer
     } catch (Exception e)
     {
       StringBuffer sp = new StringBuffer();
-      for (AlignmentPanel alignPanel : _alignwith)
+      for (AlignmentViewPanel alignPanel : _alignwith)
       {
-        sp.append("'" + alignPanel.alignFrame.getTitle() + "' ");
+        sp.append("'" + alignPanel.getViewName() + "' ");
       }
       Cache.log.info("Couldn't align structures with the " + sp.toString()
               + "associated alignment panels.", e);
@@ -811,7 +791,7 @@ public abstract class StructureViewerBase extends GStructureViewer
   }
 
   @Override
-  public void background_actionPerformed(ActionEvent actionEvent)
+  public void background_actionPerformed()
   {
     Color col = JColorChooser.showDialog(this,
             MessageManager.getString("label.select_background_colour"),
@@ -823,7 +803,7 @@ public abstract class StructureViewerBase extends GStructureViewer
   }
 
   @Override
-  public void viewerColour_actionPerformed(ActionEvent actionEvent)
+  public void viewerColour_actionPerformed()
   {
     if (viewerColour.isSelected())
     {
@@ -833,21 +813,21 @@ public abstract class StructureViewerBase extends GStructureViewer
   }
 
   @Override
-  public void chainColour_actionPerformed(ActionEvent actionEvent)
+  public void chainColour_actionPerformed()
   {
     chainColour.setSelected(true);
     getBinding().colourByChain();
   }
 
   @Override
-  public void chargeColour_actionPerformed(ActionEvent actionEvent)
+  public void chargeColour_actionPerformed()
   {
     chargeColour.setSelected(true);
     getBinding().colourByCharge();
   }
 
   @Override
-  public void seqColour_actionPerformed(ActionEvent actionEvent)
+  public void seqColour_actionPerformed()
   {
     AAStructureBindingModel binding = getBinding();
     binding.setColourBySequence(seqColour.isSelected());
@@ -866,7 +846,7 @@ public abstract class StructureViewerBase extends GStructureViewer
         }
       }
       // Set the colour using the current view for the associated alignframe
-      for (AlignmentPanel alignPanel : _colourwith)
+      for (AlignmentViewPanel alignPanel : _colourwith)
       {
         binding.colourBySequence(alignPanel);
       }
@@ -875,7 +855,7 @@ public abstract class StructureViewerBase extends GStructureViewer
   }
 
   @Override
-  public void pdbFile_actionPerformed(ActionEvent actionEvent)
+  public void pdbFile_actionPerformed()
   {
     JalviewFileChooser chooser = new JalviewFileChooser(
             Cache.getProperty("LAST_DIRECTORY"));
@@ -926,7 +906,7 @@ public abstract class StructureViewerBase extends GStructureViewer
   }
 
   @Override
-  public void viewMapping_actionPerformed(ActionEvent actionEvent)
+  public void viewMapping_actionPerformed()
   {
     CutAndPasteTransfer cap = new CutAndPasteTransfer();
     try
@@ -988,7 +968,7 @@ public abstract class StructureViewerBase extends GStructureViewer
 
     if (!binding.isLoadingFromArchive())
     {
-      seqColour_actionPerformed(null);
+      seqColour_actionPerformed();
     }
   }
 
@@ -1041,4 +1021,70 @@ public abstract class StructureViewerBase extends GStructureViewer
     toFront();
   }
 
+  @Override
+  public long startProgressBar(String msg)
+  {
+    // TODO would rather have startProgress/stopProgress as the
+    // IProgressIndicator interface
+    long tm = random.nextLong();
+    if (progressBar != null)
+    {
+      progressBar.setProgressBar(msg, tm);
+    }
+    return tm;
+  }
+
+  @Override
+  public void stopProgressBar(String msg, long handle)
+  {
+    if (progressBar != null)
+    {
+      progressBar.setProgressBar(msg, handle);
+    }
+  }
+
+  protected IProgressIndicator getProgressIndicator()
+  {
+    return progressBar;
+  }
+
+  protected void setProgressIndicator(IProgressIndicator pi)
+  {
+    progressBar = pi;
+  }
+
+  protected void setProgressMessage(String message, long id)
+  {
+    if (progressBar != null)
+    {
+      progressBar.setProgressBar(message, id);
+    }
+  }
+
+  @Override
+  public void showConsole(boolean show)
+  {
+    // default does nothing
+  }
+
+  /**
+   * Show only the selected chain(s) in the viewer
+   */
+  protected void showSelectedChains()
+  {
+    List<String> toshow = new ArrayList<>();
+    for (int i = 0; i < chainMenu.getItemCount(); i++)
+    {
+      if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
+      {
+        JCheckBoxMenuItem item = (JCheckBoxMenuItem) chainMenu.getItem(i);
+        if (item.isSelected())
+        {
+          toshow.add(item.getText());
+        }
+      }
+    }
+    getBinding().showChains(toshow);
+  }
+
 }
index 2a7743a..a1529fc 100644 (file)
@@ -20,6 +20,7 @@
  */
 package jalview.gui;
 
+import jalview.api.AlignmentViewPanel;
 import jalview.util.MessageManager;
 
 import java.awt.Component;
@@ -56,7 +57,7 @@ public class ViewSelectionMenu extends JMenu
 
   private ViewSetProvider _allviews;
 
-  private List<AlignmentPanel> _selectedviews;
+  private List<AlignmentViewPanel> _selectedviews;
 
   private ItemListener _handler;
 
@@ -79,7 +80,7 @@ public class ViewSelectionMenu extends JMenu
    *          selection/deselection state
    */
   public ViewSelectionMenu(String title, final ViewSetProvider allviews,
-          final List<AlignmentPanel> selectedviews,
+          final List<AlignmentViewPanel> selectedviews,
           final ItemListener handler)
   {
     super(title);
index 6071933..8d83e75 100644 (file)
@@ -30,7 +30,6 @@ import jalview.ext.jmol.JmolCommands;
 import jalview.structure.AtomSpec;
 import jalview.structure.StructureListener;
 import jalview.structure.StructureMapping;
-import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.HttpUtils;
 
@@ -220,21 +219,22 @@ public class MouseOverStructureListener extends JSFunctionExec
 
       // Form a colour command from the given alignment panel for each distinct
       // structure
-      ArrayList<String[]> ccomands = new ArrayList<String[]>();
-      ArrayList<String> pdbfn = new ArrayList<String>();
-      StructureMappingcommandSet[] colcommands = JmolCommands
-              .getColourBySequenceCommand(ssm, modelSet, sequence, sr,
+      ArrayList<String[]> ccomands = new ArrayList<>();
+      ArrayList<String> pdbfn = new ArrayList<>();
+      String[] colcommands = new JmolCommands()
+              .colourBySequence(ssm, modelSet, sequence, sr,
                       (AlignmentViewPanel) source);
       if (colcommands == null)
       {
         return;
       }
       int sz = 0;
-      for (jalview.structure.StructureMappingcommandSet ccset : colcommands)
+      // for (jalview.structure.StructureMappingcommandSet ccset : colcommands)
+      for (String command : colcommands)
       {
-        sz += ccset.commands.length;
-        ccomands.add(ccset.commands);
-        pdbfn.add(ccset.mapping);
+        // sz += ccset.commands.length;
+        // ccomands.add(command); // ccset.commands);
+        // pdbfn.add(ccset.mapping);
       }
 
       String mclass, mhandle;
index 1b4a8bd..6c46f43 100755 (executable)
@@ -179,6 +179,8 @@ public class GPreferences extends JPanel
 
   protected JComboBox<String> structViewer = new JComboBox<>();
 
+  protected JLabel chimeraPathLabel;
+
   protected JTextField chimeraPath = new JTextField();
 
   protected ButtonGroup mappingMethod = new ButtonGroup();
@@ -1195,7 +1197,7 @@ public class GPreferences extends JPanel
     structureTab.setBorder(new TitledBorder(
             MessageManager.getString("label.structure_options")));
     structureTab.setLayout(null);
-    final int width = 400;
+    final int width = 420;
     final int height = 22;
     final int lineSpacing = 25;
     int ypos = 15;
@@ -1243,13 +1245,14 @@ public class GPreferences extends JPanel
     viewerLabel.setFont(LABEL_FONT);
     viewerLabel.setHorizontalAlignment(SwingConstants.LEFT);
     viewerLabel.setText(MessageManager.getString("label.structure_viewer"));
-    viewerLabel.setBounds(new Rectangle(10, ypos, 200, height));
+    viewerLabel.setBounds(new Rectangle(10, ypos, 220, height));
     structureTab.add(viewerLabel);
 
     structViewer.setFont(LABEL_FONT);
-    structViewer.setBounds(new Rectangle(160, ypos, 120, height));
+    structViewer.setBounds(new Rectangle(190, ypos, 120, height));
     structViewer.addItem(ViewerType.JMOL.name());
     structViewer.addItem(ViewerType.CHIMERA.name());
+    structViewer.addItem(ViewerType.CHIMERAX.name());
     structViewer.addActionListener(new ActionListener()
     {
       @Override
@@ -1262,25 +1265,28 @@ public class GPreferences extends JPanel
     structureTab.add(structViewer);
 
     ypos += lineSpacing;
-    JLabel pathLabel = new JLabel();
-    pathLabel.setFont(new java.awt.Font("SansSerif", 0, 11));
-    pathLabel.setHorizontalAlignment(SwingConstants.LEFT);
-    pathLabel.setText(MessageManager.getString("label.chimera_path"));
-    pathLabel.setBounds(new Rectangle(10, ypos, 140, height));
-    structureTab.add(pathLabel);
+    chimeraPathLabel = new JLabel();
+    chimeraPathLabel.setFont(LABEL_FONT);// new Font("SansSerif", 0, 11));
+    chimeraPathLabel.setHorizontalAlignment(SwingConstants.LEFT);
+    chimeraPathLabel.setText(MessageManager
+            .formatMessage("label.chimera_path", "Chimera(X)"));
+    chimeraPathLabel.setBounds(new Rectangle(10, ypos, 170, height));
+    chimeraPathLabel.setEnabled(false);
+    structureTab.add(chimeraPathLabel);
 
     chimeraPath.setFont(LABEL_FONT);
     chimeraPath.setText("");
+    chimeraPath.setEnabled(false);
     final String tooltip = JvSwingUtils.wrapTooltip(true,
             MessageManager.getString("label.chimera_path_tip"));
     chimeraPath.setToolTipText(tooltip);
-    chimeraPath.setBounds(new Rectangle(160, ypos, 300, height));
+    chimeraPath.setBounds(new Rectangle(190, ypos, 290, height));
     chimeraPath.addMouseListener(new MouseAdapter()
     {
       @Override
       public void mouseClicked(MouseEvent e)
       {
-        if (e.getClickCount() == 2)
+        if (chimeraPath.isEnabled() && e.getClickCount() == 2)
         {
           String chosen = openFileChooser();
           if (chosen != null)
@@ -1305,7 +1311,7 @@ public class GPreferences extends JPanel
             MessageManager.getString("label.mapping_method"));
     mmTitledBorder.setTitleFont(LABEL_FONT);
     mappingPanel.setBorder(mmTitledBorder);
-    mappingPanel.setBounds(new Rectangle(10, ypos, 452, 45));
+    mappingPanel.setBounds(new Rectangle(10, ypos, 472, 45));
     // GridLayout mappingLayout = new GridLayout();
     mappingPanel.setLayout(new GridLayout());
     mappingPanel.add(nwMapping);
@@ -1316,7 +1322,7 @@ public class GPreferences extends JPanel
     ypos += lineSpacing;
     FTSDataColumnPreferences docFieldPref = new FTSDataColumnPreferences(
             PreferenceSource.PREFERENCES, PDBFTSRestClient.getInstance());
-    docFieldPref.setBounds(new Rectangle(10, ypos, 450, 120));
+    docFieldPref.setBounds(new Rectangle(10, ypos, 470, 120));
     structureTab.add(docFieldPref);
 
     return structureTab;
index 83d8590..1fccdcb 100644 (file)
@@ -104,7 +104,7 @@ public abstract class GStructureViewer extends JInternalFrame
       @Override
       public void actionPerformed(ActionEvent actionEvent)
       {
-        pdbFile_actionPerformed(actionEvent);
+        pdbFile_actionPerformed();
       }
     });
 
@@ -115,7 +115,7 @@ public abstract class GStructureViewer extends JInternalFrame
       @Override
       public void actionPerformed(ActionEvent actionEvent)
       {
-        png_actionPerformed(actionEvent);
+        png_actionPerformed();
       }
     });
 
@@ -126,7 +126,7 @@ public abstract class GStructureViewer extends JInternalFrame
       @Override
       public void actionPerformed(ActionEvent actionEvent)
       {
-        eps_actionPerformed(actionEvent);
+        eps_actionPerformed();
       }
     });
 
@@ -137,7 +137,7 @@ public abstract class GStructureViewer extends JInternalFrame
       @Override
       public void actionPerformed(ActionEvent actionEvent)
       {
-        viewMapping_actionPerformed(actionEvent);
+        viewMapping_actionPerformed();
       }
     });
 
@@ -167,7 +167,7 @@ public abstract class GStructureViewer extends JInternalFrame
       @Override
       public void actionPerformed(ActionEvent actionEvent)
       {
-        showHelp_actionPerformed(actionEvent);
+        showHelp_actionPerformed();
       }
     });
     alignStructs = new JMenuItem();
@@ -178,7 +178,7 @@ public abstract class GStructureViewer extends JInternalFrame
       @Override
       public void actionPerformed(ActionEvent actionEvent)
       {
-        alignStructs_actionPerformed(actionEvent);
+        alignStructsWithAllAlignPanels();
       }
     });
 
@@ -216,54 +216,53 @@ public abstract class GStructureViewer extends JInternalFrame
   {
   }
 
-  protected void viewerColour_actionPerformed(ActionEvent actionEvent)
+  protected void viewerColour_actionPerformed()
   {
   }
 
-  protected abstract String alignStructs_actionPerformed(
-          ActionEvent actionEvent);
+  protected abstract String alignStructsWithAllAlignPanels();
 
-  public void pdbFile_actionPerformed(ActionEvent actionEvent)
+  public void pdbFile_actionPerformed()
   {
 
   }
 
-  public void png_actionPerformed(ActionEvent actionEvent)
+  public void png_actionPerformed()
   {
 
   }
 
-  public void eps_actionPerformed(ActionEvent actionEvent)
+  public void eps_actionPerformed()
   {
 
   }
 
-  public void viewMapping_actionPerformed(ActionEvent actionEvent)
+  public void viewMapping_actionPerformed()
   {
 
   }
 
-  public void seqColour_actionPerformed(ActionEvent actionEvent)
+  public void seqColour_actionPerformed()
   {
 
   }
 
-  public void chainColour_actionPerformed(ActionEvent actionEvent)
+  public void chainColour_actionPerformed()
   {
 
   }
 
-  public void chargeColour_actionPerformed(ActionEvent actionEvent)
+  public void chargeColour_actionPerformed()
   {
 
   }
 
-  public void background_actionPerformed(ActionEvent actionEvent)
+  public void background_actionPerformed()
   {
 
   }
 
-  public void showHelp_actionPerformed(ActionEvent actionEvent)
+  public void showHelp_actionPerformed()
   {
 
   }
index ca0423b..df4a93e 100644 (file)
@@ -2110,7 +2110,7 @@ public class Jalview2XML
           final String viewId = viewFrame.getViewId();
           state.setViewId(viewId);
           state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
-          state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap));
+          state.setColourwithAlignPanel(viewFrame.isUsedForColourBy(ap));
           state.setColourByJmol(viewFrame.isColouredByViewer());
           state.setType(viewFrame.getViewerType().toString());
           // pdb.addStructureState(state);
index f20cd31..8b8161f 100644 (file)
@@ -43,52 +43,70 @@ public class AtomSpec
    * Parses a Chimera atomspec e.g. #1:12.A to construct an AtomSpec model (with
    * null pdb file name)
    * 
+   * <pre>
+   * Chimera format: 
+   *    #1.2:12-20.A     model 1, submodel 2, chain A, atoms 12-20
+   * ChimeraX format:
+   *    #1.2/A:12-20
+   * </pre>
+   * 
    * @param spec
+   * @param chimeraX
    * @return
    * @throw IllegalArgumentException if the spec cannot be parsed, or represents
    *        more than one residue
+   * @see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec.html
+   * @see http://rbvi.ucsf.edu/chimerax/docs/user/commands/atomspec.html
    */
-  public static AtomSpec fromChimeraAtomspec(String spec)
+  public static AtomSpec fromChimeraAtomspec(String spec, boolean chimeraX)
   {
-    int colonPos = spec.indexOf(":");
-    if (colonPos == -1)
+    int modelSeparatorPos = spec.indexOf(chimeraX ? "/" : ":");
+    if (modelSeparatorPos == -1)
     {
       throw new IllegalArgumentException(spec);
     }
 
     int hashPos = spec.indexOf("#");
-    if (hashPos == -1 && colonPos != 0)
+    if (hashPos == -1 && modelSeparatorPos != 0)
     {
       // # is missing but something precedes : - reject
       throw new IllegalArgumentException(spec);
     }
 
-    String modelSubmodel = spec.substring(hashPos + 1, colonPos);
-    int dotPos = modelSubmodel.indexOf(".");
+    String modelSubmodel = spec.substring(hashPos + 1, modelSeparatorPos);
     int modelId = 0;
     try
     {
-      modelId = Integer.valueOf(dotPos == -1 ? modelSubmodel
-              : modelSubmodel.substring(0, dotPos));
+      int subModelPos = modelSubmodel.indexOf(".");
+      modelId = Integer.valueOf(
+              subModelPos > 0 ? modelSubmodel.substring(0, subModelPos)
+                      : modelSubmodel);
     } catch (NumberFormatException e)
     {
       // ignore, default to model 0
     }
 
-    String residueChain = spec.substring(colonPos + 1);
-    dotPos = residueChain.indexOf(".");
+    /*
+     * now process what follows the model, either
+     * Chimera:  atoms.chain
+     * ChimeraX: chain:atoms
+     */
+    String atomsAndChain = spec.substring(modelSeparatorPos + 1);
+    String[] tokens = atomsAndChain.split(chimeraX ? "\\:" : "\\.");
+    String atoms = tokens.length == 1 ? atomsAndChain
+            : (chimeraX ? tokens[1] : tokens[0]);
     int resNum = 0;
     try
     {
-      resNum = Integer.parseInt(dotPos == -1 ? residueChain
-              : residueChain.substring(0, dotPos));
+      resNum = Integer.parseInt(atoms);
     } catch (NumberFormatException e)
     {
       // could be a range e.g. #1:4-7.B
       throw new IllegalArgumentException(spec);
     }
 
-    String chainId = dotPos == -1 ? "" : residueChain.substring(dotPos + 1);
+    String chainId = tokens.length == 1 ? ""
+            : (chimeraX ? tokens[0] : tokens[1]);
 
     return new AtomSpec(modelId, chainId, resNum, 0);
   }
diff --git a/src/jalview/structure/AtomSpecModel.java b/src/jalview/structure/AtomSpecModel.java
new file mode 100644 (file)
index 0000000..1b7d284
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * 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.structure;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * A class to model a set of models, chains and atom range positions
+ * 
+ */
+public class AtomSpecModel
+{
+  /*
+   * { modelNo, {chainCode, List<from-to> ranges} }
+   */
+  private Map<Integer, Map<String, BitSet>> atomSpec;
+
+  /**
+   * Constructor
+   */
+  public AtomSpecModel()
+  {
+    atomSpec = new TreeMap<>();
+  }
+
+  /**
+   * Adds one contiguous range to this atom spec
+   * 
+   * @param model
+   * @param startPos
+   * @param endPos
+   * @param chain
+   */
+  public void addRange(int model, int startPos, int endPos, String chain)
+  {
+    /*
+     * Get/initialize map of data for the colour and model
+     */
+    Map<String, BitSet> modelData = atomSpec.get(model);
+    if (modelData == null)
+    {
+      atomSpec.put(model, modelData = new TreeMap<>());
+    }
+
+    /*
+     * Get/initialize map of data for colour, model and chain
+     */
+    BitSet chainData = modelData.get(chain);
+    if (chainData == null)
+    {
+      chainData = new BitSet();
+      modelData.put(chain, chainData);
+    }
+
+    /*
+     * Add the start/end positions
+     */
+    chainData.set(startPos, endPos + 1);
+  }
+
+  public Iterable<Integer> getModels()
+  {
+    return atomSpec.keySet();
+  }
+
+  public Iterable<String> getChains(Integer model)
+  {
+    return atomSpec.containsKey(model) ? atomSpec.get(model).keySet()
+            : null;
+  }
+
+  /**
+   * Returns a (possibly empty) ordered list of contiguous atom ranges for the
+   * given model and chain.
+   * 
+   * @param model
+   * @param chain
+   * @return
+   */
+  public List<int[]> getRanges(Integer model, String chain)
+  {
+    List<int[]> ranges = new ArrayList<>();
+    if (atomSpec.containsKey(model))
+    {
+      BitSet bs = atomSpec.get(model).get(chain);
+      int start = 0;
+      if (bs != null)
+      {
+        start = bs.nextSetBit(start);
+        int end = 0;
+        while (start != -1)
+        {
+          end = bs.nextClearBit(start);
+          ranges.add(new int[] { start, end - 1 });
+          start = bs.nextSetBit(end);
+        }
+      }
+    }
+    return ranges;
+  }
+}
diff --git a/src/jalview/structure/StructureCommandsBase.java b/src/jalview/structure/StructureCommandsBase.java
new file mode 100644 (file)
index 0000000..44764db
--- /dev/null
@@ -0,0 +1,209 @@
+package jalview.structure;
+
+import jalview.api.AlignViewportI;
+import jalview.api.AlignmentViewPanel;
+import jalview.api.FeatureRenderer;
+import jalview.api.SequenceRenderer;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.SequenceI;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
+import jalview.util.Comparison;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * A base class holding methods useful to all classes that implement commands
+ * for structure viewers
+ * 
+ * @author gmcarstairs
+ *
+ */
+public abstract class StructureCommandsBase implements StructureCommandsI
+{
+  private static final String CMD_SEPARATOR = ";";
+
+  /**
+   * Returns something that separates concatenated commands
+   * 
+   * @return
+   */
+  protected static String getCommandSeparator()
+  {
+    return CMD_SEPARATOR;
+  }
+
+  @Override
+  public String[] setAttributesForFeatures(StructureSelectionManager ssm,
+          String[] files, SequenceI[][] sequence, AlignmentViewPanel avp)
+  {
+    // default does nothing, override where this is implemented
+    return null;
+  }
+
+  /**
+   * Returns the lowest model number used by the structure viewer
+   * 
+   * @return
+   */
+  @Override
+  public int getModelStartNo()
+  {
+    return 0;
+  }
+
+  /**
+   * Helper method to add one contiguous range to the AtomSpec model for the given
+   * value (creating the model if necessary). As used by Jalview, {@code value} is
+   * <ul>
+   * <li>a colour, when building a 'colour structure by sequence' command</li>
+   * <li>a feature value, when building a 'set Chimera attributes from features'
+   * command</li>
+   * </ul>
+   * 
+   * @param map
+   * @param value
+   * @param model
+   * @param startPos
+   * @param endPos
+   * @param chain
+   */
+  public static final void addAtomSpecRange(Map<Object, AtomSpecModel> map,
+          Object value,
+          int model, int startPos, int endPos, String chain)
+  {
+    /*
+     * Get/initialize map of data for the colour
+     */
+    AtomSpecModel atomSpec = map.get(value);
+    if (atomSpec == null)
+    {
+      atomSpec = new AtomSpecModel();
+      map.put(value, atomSpec);
+    }
+  
+    atomSpec.addRange(model, startPos, endPos, chain);
+  }
+
+  /**
+   * Traverse the map of colours/models/chains/positions to construct a list of
+   * 'color' commands (one per distinct colour used). The format of each command
+   * is specific to the structure viewer.
+   * 
+   * @param colourMap
+   * @return
+   */
+  @Override
+  public String[] colourBySequence(Map<Object, AtomSpecModel> colourMap)
+  {
+    /*
+     * This version concatenates all commands into a single String (semi-colon
+     * delimited). If length limit issues arise, refactor to return one color
+     * command per colour.
+     */
+    List<String> commands = new ArrayList<>();
+    StringBuilder sb = new StringBuilder(256);
+    boolean firstColour = true;
+    for (Object key : colourMap.keySet())
+    {
+      Color colour = (Color) key;
+      if (!firstColour)
+      {
+        sb.append(getCommandSeparator()).append(" ");
+      }
+      firstColour = false;
+      final AtomSpecModel colourData = colourMap.get(colour);
+      sb.append(getColourCommand(colourData, colour));
+    }
+    commands.add(sb.toString());
+
+    return commands.toArray(new String[commands.size()]);
+  }
+
+  /**
+   * Returns a command to colour the atoms represented by {@code atomSpecModel}
+   * with the colour specified by {@code colourCode}.
+   * 
+   * @param atomSpecModel
+   * @param colour
+   * @return
+   */
+  protected String getColourCommand(AtomSpecModel atomSpecModel, Color colour)
+  {
+    String atomSpec = getAtomSpec(atomSpecModel, false);
+    return getColourCommand(atomSpec, colour);
+  }
+
+  /**
+   * Returns a command to colour the atoms described (in viewer command syntax)
+   * by {@code atomSpec} with the colour specified by {@code colourCode}
+   * 
+   * @param atomSpec
+   * @param colour
+   * @return
+   */
+  protected abstract String getColourCommand(String atomSpec, Color colour);
+
+  @Override
+  public String colourByResidues(Map<String, Color> colours)
+  {
+    StringBuilder cmd = new StringBuilder(12 * colours.size());
+  
+    for (Entry<String, Color> entry : colours.entrySet())
+    {
+      String residue = entry.getKey();
+      String atomSpec = getResidueSpec(residue);
+      cmd.append(getColourCommand(atomSpec, entry.getValue()));
+      cmd.append(getCommandSeparator());
+    }
+    return cmd.toString();
+  }
+
+  /**
+   * Helper method to append one start-end range to an atomspec string
+   * 
+   * @param sb
+   * @param start
+   * @param end
+   * @param chain
+   * @param firstPositionForModel
+   */
+  protected void appendRange(StringBuilder sb, int start, int end,
+          String chain, boolean firstPositionForModel, boolean isChimeraX)
+  {
+    if (!firstPositionForModel)
+    {
+      sb.append(",");
+    }
+    if (end == start)
+    {
+      sb.append(start);
+    }
+    else
+    {
+      sb.append(start).append("-").append(end);
+    }
+
+    if (!isChimeraX)
+    {
+      sb.append(".");
+      if (!" ".equals(chain))
+      {
+        sb.append(chain);
+      }
+    }
+  }
+
+  /**
+   * Returns the atom specifier meaning all occurrences of the given residue
+   * 
+   * @param residue
+   * @return
+   */
+  protected abstract String getResidueSpec(String residue);
+}
diff --git a/src/jalview/structure/StructureCommandsFactory.java b/src/jalview/structure/StructureCommandsFactory.java
new file mode 100644 (file)
index 0000000..9319427
--- /dev/null
@@ -0,0 +1,32 @@
+package jalview.structure;
+
+import jalview.ext.jmol.JmolCommands;
+import jalview.ext.rbvi.chimera.ChimeraCommands;
+import jalview.ext.rbvi.chimera.ChimeraXCommands;
+import jalview.gui.StructureViewer.ViewerType;
+
+/**
+ * A factory that serves a class that can generate structure commands for a
+ * specified structure viewer
+ */
+public class StructureCommandsFactory
+{
+  public StructureCommandsI getStructureCommands(ViewerType viewer)
+  {
+    StructureCommandsI commands = null;
+    switch (viewer)
+    {
+    case JMOL:
+      commands = new JmolCommands();
+      break;
+    case CHIMERA:
+      commands = new ChimeraCommands();
+      break;
+    case CHIMERAX:
+      commands = new ChimeraXCommands();
+      break;
+    default:
+    }
+    return commands;
+  }
+}
diff --git a/src/jalview/structure/StructureCommandsI.java b/src/jalview/structure/StructureCommandsI.java
new file mode 100644 (file)
index 0000000..359eac6
--- /dev/null
@@ -0,0 +1,189 @@
+package jalview.structure;
+
+import jalview.api.AlignmentViewPanel;
+import jalview.datamodel.SequenceI;
+
+import java.awt.Color;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Methods that generate commands that can be sent to a molecular structure
+ * viewer program (e.g. Jmol, Chimera, ChimeraX)
+ * 
+ * @author gmcarstairs
+ *
+ */
+public interface StructureCommandsI
+{
+  /**
+   * Data bean class to simplify parameterisation in superposeStructures
+   */
+  public class SuperposeData
+  {
+    public String filename;
+
+    public String pdbId;
+
+    public String chain = "";
+
+    public boolean isRna;
+
+    /*
+     * The pdb residue number (if any) mapped to columns of the alignment
+     */
+    public int[] pdbResNo; // or use SparseIntArray?
+
+    public int modelNo;
+
+    /**
+     * Constructor
+     * 
+     * @param width
+     *          width of alignment (number of columns that may potentially
+     *          participate in superposition)
+     * @param model
+     *          structure viewer model number
+     */
+    public SuperposeData(int width, int model)
+    {
+      pdbResNo = new int[width];
+      modelNo = model;
+    }
+  }
+
+  /**
+   * Returns the command to colour by chain
+   * 
+   * @return
+   */
+  String colourByChain();
+
+  /**
+   * Returns the command to colour residues using a charge-based scheme:
+   * <ul>
+   * <li>Aspartic acid and Glutamic acid (negative charge) red</li>
+   * <li>Lysine and Arginine (positive charge) blue</li>
+   * <li>Cysteine - yellow</li>
+   * <li>all others - white</li>
+   * </ul>
+   * 
+   * @return
+   */
+  String colourByCharge();
+
+  /**
+   * Returns the command to colour residues with the colours provided in the
+   * map, one per three letter residue code
+   * 
+   * @param colours
+   * @return
+   */
+  String colourByResidues(Map<String, Color> colours);
+
+  /**
+   * Returns the command to set the background colour of the structure viewer
+   * 
+   * @param col
+   * @return
+   */
+  String setBackgroundColour(Color col);
+
+  /**
+   * Returns commands to colour mapped residues of structures according to
+   * Jalview's colouring (including feature colouring if applied). Parameter is
+   * a map from Color to a model of all residues assigned that colour.
+   * 
+   * @param colourMap
+   * @return
+   */
+
+  String[] colourBySequence(Map<Object, AtomSpecModel> colourMap);
+
+  /**
+   * Returns a command to centre the display in the structure viewer
+   * 
+   * @return
+   */
+  String focusView();
+
+  /**
+   * Returns a command to show only the selected chains. The items in the input
+   * list should be formatted as "modelno:chainid".
+   * 
+   * @param toShow
+   * @return
+   */
+  String showChains(List<String> toShow);
+
+  /**
+   * Returns zero, one or more commands to set attributes on mapped residues in
+   * the structure viewer for any features present and displayed in Jalview
+   * 
+   * @param ssm
+   * @param files
+   * @param sequence
+   * @param avp
+   * @return
+   */
+  String[] setAttributesForFeatures(StructureSelectionManager ssm,
+          String[] files, SequenceI[][] sequence, AlignmentViewPanel avp);
+
+  /**
+   * Returns a command to superpose structures by closest positioning of
+   * residues in {@code atomSpec} to the corresponding residues in {@ refAtoms}.
+   * If wanted, this may include commands to visually highlight the residues
+   * that were used for the superposition.
+   * 
+   * @param refAtoms
+   * @param atomSpec
+   * @return
+   */
+  String superposeStructures(AtomSpecModel refAtoms,
+          AtomSpecModel atomSpec);
+
+  /**
+   * Returns a command to open a file of commands at the given path
+   * 
+   * @param path
+   * @return
+   */
+  String openCommandFile(String path);
+
+  /**
+   * Returns a command to save the current viewer session state to the given
+   * file
+   * 
+   * @param filepath
+   * @return
+   */
+  String saveSession(String filepath);
+
+  /**
+   * Returns a representation of the atom set represented by the model, in
+   * viewer syntax format. If {@code alphaOnly} is true, this is restricted to
+   * Alpha Carbon (peptide) or Phosphorous (rna) only
+   * 
+   * @param model
+   * @param alphaOnly
+   * @return
+   */
+  String getAtomSpec(AtomSpecModel model, boolean alphaOnly);
+
+  /**
+   * Returns the lowest model number used by the structure viewer (likely 0 or
+   * 1)
+   * 
+   * @return
+   */
+  // TODO remove by refactoring so command generation is purely driven by
+  // AtomSpecModel objects derived in the binding classes?
+  int getModelStartNo();
+
+  /**
+   * Show only the backbone of the peptide (cartoons in Jmol, chain in Chimera)
+   * 
+   * @return
+   */
+  String showBackbone();
+}
index 2528286..0c1cd50 100644 (file)
@@ -20,7 +20,9 @@
  */
 package jalview.structures.models;
 
+import jalview.api.AlignViewportI;
 import jalview.api.AlignmentViewPanel;
+import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.api.StructureSelectionManagerProvider;
 import jalview.api.structures.JalviewStructureDisplayI;
@@ -28,12 +30,17 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
+import jalview.gui.StructureViewer.ViewerType;
 import jalview.io.DataSourceType;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
 import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ResidueProperties;
 import jalview.structure.AtomSpec;
+import jalview.structure.AtomSpecModel;
+import jalview.structure.StructureCommandsI;
+import jalview.structure.StructureCommandsI.SuperposeData;
 import jalview.structure.StructureListener;
 import jalview.structure.StructureMapping;
-import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.Comparison;
 import jalview.util.MessageManager;
@@ -42,7 +49,12 @@ import java.awt.Color;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
+
+import javax.swing.SwingUtilities;
 
 /**
  * 
@@ -57,10 +69,35 @@ public abstract class AAStructureBindingModel
         extends SequenceStructureBindingModel
         implements StructureListener, StructureSelectionManagerProvider
 {
+  private static final int MIN_POS_TO_SUPERPOSE = 4;
+
+  private static final String COLOURING_STRUCTURES = MessageManager
+          .getString("status.colouring_structures");
+
+  /*
+   * the Jalview panel through which the user interacts
+   * with the structure viewer
+   */
+  private JalviewStructureDisplayI viewer;
+
+  /*
+   * helper that generates command syntax
+   */
+  private StructureCommandsI commandGenerator;
 
   private StructureSelectionManager ssm;
 
   /*
+   * modelled chains, formatted as "pdbid:chainCode"
+   */
+  private List<String> chainNames;
+
+  /*
+   * lookup of pdb file name by key "pdbid:chainCode"
+   */
+  private Map<String, String> chainFile;
+
+  /*
    * distinct PDB entries (pdb files) associated
    * with sequences
    */
@@ -95,35 +132,6 @@ public abstract class AAStructureBindingModel
   public String fileLoadingError;
 
   /**
-   * Data bean class to simplify parameterisation in superposeStructures
-   */
-  protected class SuperposeData
-  {
-    /**
-     * Constructor with alignment width argument
-     * 
-     * @param width
-     */
-    public SuperposeData(int width)
-    {
-      pdbResNo = new int[width];
-    }
-
-    public String filename;
-
-    public String pdbId;
-
-    public String chain = "";
-
-    public boolean isRna;
-
-    /*
-     * The pdb residue number (if any) mapped to each column of the alignment
-     */
-    public int[] pdbResNo;
-  }
-
-  /**
    * Constructor
    * 
    * @param ssm
@@ -134,6 +142,8 @@ public abstract class AAStructureBindingModel
   {
     this.ssm = ssm;
     this.sequence = seqs;
+    chainNames = new ArrayList<>();
+    chainFile = new HashMap<>();
   }
 
   /**
@@ -148,8 +158,7 @@ public abstract class AAStructureBindingModel
           PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
           DataSourceType protocol)
   {
-    this.ssm = ssm;
-    this.sequence = sequenceIs;
+    this(ssm, sequenceIs);
     this.nucleotide = Comparison.isNucleotide(sequenceIs);
     this.pdbEntry = pdbentry;
     this.protocol = protocol;
@@ -331,7 +340,11 @@ public abstract class AAStructureBindingModel
    */
   protected void releaseUIResources()
   {
+  }
 
+  @Override
+  public void releaseReferences(Object svl)
+  {
   }
 
   public boolean isColourBySequence()
@@ -339,6 +352,25 @@ public abstract class AAStructureBindingModel
     return colourBySequence;
   }
 
+  /**
+   * Called when the binding thinks the UI needs to be refreshed after a
+   * structure viewer state change. This could be because structures were
+   * loaded, or because an error has occurred. Default does nothing, override as
+   * required.
+   */
+  public void refreshGUI()
+  {
+  }
+
+  /**
+   * Instruct the Jalview binding to update the pdbentries vector if necessary
+   * prior to matching the jmol view's contents to the list of structure files
+   * Jalview knows about. By default does nothing, override as required.
+   */
+  public void refreshPdbEntries()
+  {
+  }
+
   public void setColourBySequence(boolean colourBySequence)
   {
     this.colourBySequence = colourBySequence;
@@ -355,8 +387,8 @@ public abstract class AAStructureBindingModel
               { Integer.valueOf(pe).toString() }));
     }
     final String nullChain = "TheNullChain";
-    List<SequenceI> s = new ArrayList<SequenceI>();
-    List<String> c = new ArrayList<String>();
+    List<SequenceI> s = new ArrayList<>();
+    List<String> c = new ArrayList<>();
     if (getChains() == null)
     {
       setChains(new String[getPdbCount()][]);
@@ -425,8 +457,8 @@ public abstract class AAStructureBindingModel
   public synchronized PDBEntry[] addSequenceAndChain(PDBEntry[] pdbe,
           SequenceI[][] seq, String[][] chns)
   {
-    List<PDBEntry> v = new ArrayList<PDBEntry>();
-    List<int[]> rtn = new ArrayList<int[]>();
+    List<PDBEntry> v = new ArrayList<>();
+    List<int[]> rtn = new ArrayList<>();
     for (int i = 0; i < getPdbCount(); i++)
     {
       v.add(getPdbEntry(i));
@@ -639,7 +671,7 @@ public abstract class AAStructureBindingModel
              * for the same structure)
              */
             s = seqCountForPdbFile;
-            break;
+            break; // fixme break out of two loops here!
           }
         }
       }
@@ -732,11 +764,15 @@ public abstract class AAStructureBindingModel
   }
 
   /**
-   * Returns a list of chains mapped in this viewer.
+   * Returns a list of chains mapped in this viewer, formatted as
+   * "pdbid:chainCode"
    * 
    * @return
    */
-  public abstract List<String> getChainNames();
+  public List<String> getChainNames()
+  {
+    return chainNames;
+  }
 
   /**
    * Returns the Jalview panel hosting the structure viewer (if any)
@@ -745,34 +781,132 @@ public abstract class AAStructureBindingModel
    */
   public JalviewStructureDisplayI getViewer()
   {
-    return null;
+    return viewer;
   }
 
-  public abstract void setJalviewColourScheme(ColourSchemeI cs);
+  public void setViewer(JalviewStructureDisplayI v)
+  {
+    viewer = v;
+  }
 
   /**
    * Constructs and sends a command to align structures against a reference
    * structure, based on one or more sequence alignments. May optionally return
-   * an error or warning message for the alignment command.
-   * 
-   * @param alignments
-   *          an array of alignments to process
-   * @param structureIndices
-   *          an array of corresponding reference structures (index into pdb
-   *          file array); if a negative value is passed, the first PDB file
-   *          mapped to an alignment sequence is used as the reference for
-   *          superposition
-   * @param hiddenCols
-   *          an array of corresponding hidden columns for each alignment
+   * an error or warning message for the alignment command(s).
+   * 
+   * @param alignWith
+   *          an array of one or more alignment views to process
    * @return
    */
-  public abstract String superposeStructures(AlignmentI[] alignments,
-          int[] structureIndices, HiddenColumns[] hiddenCols);
+  public String superposeStructures(List<AlignmentViewPanel> alignWith)
+  {
+    String error = "";
+    String[] files = getStructureFiles();
+
+    if (!waitForFileLoad(files))
+    {
+      return null;
+    }
+    refreshPdbEntries();
+
+    for (AlignmentViewPanel view : alignWith)
+    {
+      AlignmentI alignment = view.getAlignment();
+      HiddenColumns hiddenCols = alignment.getHiddenColumns();
 
-  public abstract void setBackgroundColour(Color col);
+      /*
+       * 'matched' bit i will be set for visible alignment columns i where
+       * all sequences have a residue with a mapping to their PDB structure
+       */
+      BitSet matched = new BitSet();
+      final int width = alignment.getWidth();
+      for (int m = 0; m < width; m++)
+      {
+        if (hiddenCols == null || hiddenCols.isVisible(m))
+        {
+          matched.set(m);
+        }
+      }
 
-  protected abstract StructureMappingcommandSet[] getColourBySequenceCommands(
-          String[] files, SequenceRenderer sr, AlignmentViewPanel avp);
+      SuperposeData[] structures = new SuperposeData[files.length];
+      for (int f = 0; f < files.length; f++)
+      {
+        structures[f] = new SuperposeData(width,
+                f + commandGenerator.getModelStartNo());
+      }
+
+      /*
+       * Calculate the superposable alignment columns ('matched'), and the
+       * corresponding structure residue positions (structures.pdbResNo)
+       */
+      int refStructure = findSuperposableResidues(alignment,
+              matched, structures);
+
+      /*
+       * require at least 4 positions to be able to execute superposition
+       */
+      int nmatched = matched.cardinality();
+      if (nmatched < MIN_POS_TO_SUPERPOSE)
+      {
+        String msg = MessageManager.formatMessage("label.insufficient_residues",
+                nmatched);
+        error += view.getViewName() + ": " + msg + "; ";
+        continue;
+      }
+
+      /*
+       * get a model of the superposable residues in the reference structure 
+       */
+      AtomSpecModel refAtoms = getAtomSpec(structures[refStructure],
+              matched);
+
+      /*
+       * Show all as backbone before doing superposition(s)
+       * (residues used for matching will be shown as ribbon)
+       */
+      executeCommand(commandGenerator.showBackbone(), false);
+
+      /*
+       * superpose each (other) structure to the reference in turn
+       */
+      for (int i = 0; i < structures.length; i++)
+      {
+        if (i != refStructure)
+        {
+          AtomSpecModel atomSpec = getAtomSpec(structures[i], matched);
+          String commands = commandGenerator.superposeStructures(refAtoms,
+                  atomSpec);
+          List<String> replies = executeCommands(true, commands);
+          for (String reply : replies)
+          {
+            // return this error (Chimera only) to the user
+            if (reply.toLowerCase().contains("unequal numbers of atoms"))
+            {
+              error += "; " + reply;
+            }
+          }
+        }
+      }
+    }
+
+    return error;
+  }
+
+  private AtomSpecModel getAtomSpec(SuperposeData superposeData,
+          BitSet matched)
+  {
+    AtomSpecModel model = new AtomSpecModel();
+    int nextColumnMatch = matched.nextSetBit(0);
+    while (nextColumnMatch != -1)
+    {
+      int pdbResNum = superposeData.pdbResNo[nextColumnMatch];
+      model.addRange(superposeData.modelNo, pdbResNum, pdbResNum,
+              superposeData.chain);
+      nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1);
+    }
+
+    return model;
+  }
 
   /**
    * returns the current sequenceRenderer that should be used to colour the
@@ -785,12 +919,179 @@ public abstract class AAStructureBindingModel
   public abstract SequenceRenderer getSequenceRenderer(
           AlignmentViewPanel alignment);
 
-  protected abstract void colourBySequence(
-          StructureMappingcommandSet[] colourBySequenceCommands);
+  /**
+   * Sends a command to the structure viewer to colour each chain with a
+   * distinct colour (to the extent supported by the viewer)
+   */
+  public void colourByChain()
+  {
+    colourBySequence = false;
+
+    // TODO: JAL-628 colour chains distinctly across all visible models
+
+    executeCommand(commandGenerator.colourByChain(), false,
+            COLOURING_STRUCTURES);
+  }
+
+  /**
+   * Sends a command to the structure viewer to colour each chain with a
+   * distinct colour (to the extent supported by the viewer)
+   */
+  public void colourByCharge()
+  {
+    colourBySequence = false;
+
+    executeCommand(commandGenerator.colourByCharge(), false,
+            COLOURING_STRUCTURES);
+  }
+
+  /**
+   * Sends a command to the structure to apply a colour scheme (defined in
+   * Jalview but not necessarily applied to the alignment), which defines a
+   * colour per residue letter. More complex schemes (e.g. that depend on
+   * consensus) cannot be used here and are ignored.
+   * 
+   * @param cs
+   */
+  public void colourByJalviewColourScheme(ColourSchemeI cs)
+  {
+    colourBySequence = false;
+
+    if (cs == null || !cs.isSimple())
+    {
+      return;
+    }
+    
+    /*
+     * build a map of {Residue3LetterCode, Color}
+     */
+    Map<String, Color> colours = new HashMap<>();
+    List<String> residues = ResidueProperties.getResidues(isNucleotide(),
+            false);
+    for (String resName : residues)
+    {
+      char res = resName.length() == 3
+              ? ResidueProperties.getSingleCharacterCode(resName)
+              : resName.charAt(0);
+      Color colour = cs.findColour(res, 0, null, null, 0f);
+      colours.put(resName, colour);
+    }
+
+    /*
+     * pass to the command constructor, and send the command
+     */
+    String cmd = commandGenerator.colourByResidues(colours);
+    executeCommand(cmd, false, COLOURING_STRUCTURES);
+  }
+
+  public void setBackgroundColour(Color col)
+  {
+    String cmd = commandGenerator.setBackgroundColour(col);
+    executeCommand(cmd, false, null);
+  }
+
+  /**
+   * Sends one command to the structure viewer. If {@code getReply} is true, the
+   * command is sent synchronously, otherwise in a deferred thread.
+   * <p>
+   * If a progress message is supplied, this is displayed before command
+   * execution, and removed afterwards.
+   * 
+   * @param cmd
+   * @param getReply
+   * @param msg
+   * @return
+   */
+  private List<String> executeCommand(String cmd, boolean getReply,
+          String msg)
+  {
+    if (getReply)
+    {
+      return executeSynchronous(cmd, msg, getReply);
+    }
+    else
+    {
+      executeAsynchronous(cmd, msg);
+      return null;
+    }
+  }
+
+  /**
+   * Sends the command in the current thread. If a message is supplied, this is
+   * shown before the thread is started, and removed when it completes. May
+   * return a reply to the command if requested.
+   * 
+   * @param cmd
+   * @param msg
+   * @param getReply
+   * @return
+   */
+  private List<String> executeSynchronous(String cmd, String msg, boolean getReply)
+  {
+    final JalviewStructureDisplayI theViewer = getViewer();
+    final long handle = msg == null ? 0 : theViewer.startProgressBar(msg);
+    try
+    {
+      return executeCommand(cmd, getReply);
+    } finally
+    {
+      if (msg != null)
+      {
+        theViewer.stopProgressBar(null, handle);
+      }
+    }
+  }
+
+  /**
+   * Sends the command in a separate thread. If a message is supplied, this is
+   * shown before the thread is started, and removed when it completes. No value
+   * is returned.
+   * 
+   * @param cmd
+   * @param msg
+   */
+  private void executeAsynchronous(String cmd, String msg)
+  {
+    final JalviewStructureDisplayI theViewer = getViewer();
+    final long handle = msg == null ? 0 : theViewer.startProgressBar(msg);
+
+    SwingUtilities.invokeLater(new Runnable()
+    {
+      @Override
+      public void run()
+      {
+        try
+        {
+          executeCommand(cmd, false);
+        } finally
+        {
+          if (msg != null)
+          {
+            theViewer.stopProgressBar(null, handle);
+          }
+        }
+      }
+    });
+  }
 
-  public abstract void colourByChain();
+  protected abstract List<String> executeCommand(String command,
+          boolean getReply);
 
-  public abstract void colourByCharge();
+  protected List<String> executeCommands(boolean getReply,
+          String... commands)
+  {
+    // todo: tidy this up
+    List<String> response = getReply ? new ArrayList<>() : null;
+    for (String cmd : commands)
+    {
+      List<String> replies = executeCommand(cmd, getReply);
+      if (getReply && replies != null)
+      {
+        response.addAll(replies);
+      }
+    }
+    return response;
+  }
 
   /**
    * colour any structures associated with sequences in the given alignment
@@ -810,17 +1111,313 @@ public abstract class AAStructureBindingModel
     String[] files = getStructureFiles();
 
     SequenceRenderer sr = getSequenceRenderer(alignmentv);
+    Map<Object, AtomSpecModel> colourMap = buildColoursMap(ssm, files,
+            sequence, sr, alignmentv);
+
+    String[] colourBySequenceCommands = commandGenerator
+            .colourBySequence(colourMap);
+    executeCommands(false, colourBySequenceCommands);
+  }
+
+  /**
+   * Centre the display in the structure viewer
+   */
+  public void focusView()
+  {
+    executeCommand(commandGenerator.focusView(), false);
+  }
+
+  /**
+   * Generates and executes a command to show only specified chains in the
+   * structure viewer. The list of chains to show should contain entries
+   * formatted as "pdbid:chaincode".
+   * 
+   * @param toShow
+   */
+  public void showChains(List<String> toShow)
+  {
+    // todo or reformat toShow list entries as modelNo:pdbId:chainCode ?
 
-    StructureMappingcommandSet[] colourBySequenceCommands = getColourBySequenceCommands(
-            files, sr, alignmentv);
-    colourBySequence(colourBySequenceCommands);
+    /*
+     * Reformat the pdbid:chainCode values as modelNo:chainCode
+     * since this is what is needed to construct the viewer command
+     * todo: find a less messy way to do this
+     */
+    List<String> showThese = new ArrayList<>();
+    for (String chainId : toShow)
+    {
+      String[] tokens = chainId.split("\\:");
+      if (tokens.length == 2)
+      {
+        String pdbFile = getFileForChain(chainId);
+        int modelNo = getModelNoForFile(pdbFile);
+        String model = modelNo == -1 ? "" : String.valueOf(modelNo);
+        showThese.add(model + ":" + tokens[1]);
+      }
+    }
+    executeCommand(commandGenerator.showChains(showThese), false);
   }
 
+  /**
+   * Answers the structure viewer's model number given a PDB file name. Returns
+   * -1 if model number is not found.
+   * 
+   * @param chainId
+   * @return
+   */
+  protected abstract int getModelNoForFile(String chainId);
+
   public boolean hasFileLoadingError()
   {
     return fileLoadingError != null && fileLoadingError.length() > 0;
   }
 
-  public abstract jalview.api.FeatureRenderer getFeatureRenderer(
-          AlignmentViewPanel alignment);
+  /**
+   * Returns the FeatureRenderer for the given alignment view, or null if
+   * feature display is turned off in the view.
+   * 
+   * @param avp
+   * @return
+   */
+  public FeatureRenderer getFeatureRenderer(AlignmentViewPanel avp)
+  {
+    AlignmentViewPanel ap = (avp == null) ? getViewer().getAlignmentPanel()
+            : avp;
+    return ap.getAlignViewport().isShowSequenceFeatures()
+            ? ap.getFeatureRenderer()
+            : null;
+  }
+
+  protected void setStructureCommands(StructureCommandsI cmd)
+  {
+    commandGenerator = cmd;
+  }
+
+  /**
+   * Records association of one chain id (formatted as "pdbid:chainCode") with
+   * the corresponding PDB file name
+   * 
+   * @param chainId
+   * @param fileName
+   */
+  public void addChainFile(String chainId, String fileName)
+  {
+    chainFile.put(chainId, fileName);
+  }
+
+  /**
+   * Returns the PDB filename for the given chain id (formatted as
+   * "pdbid:chainCode"), or null if not found
+   * 
+   * @param chainId
+   * @return
+   */
+  protected String getFileForChain(String chainId)
+  {
+    return chainFile.get(chainId);
+  }
+
+  @Override
+  public void updateColours(Object source)
+  {
+    AlignmentViewPanel ap = (AlignmentViewPanel) source;
+    // ignore events from panels not used to colour this view
+    if (!getViewer().isUsedForColourBy(ap))
+    {
+      return;
+    }
+    if (!isLoadingFromArchive())
+    {
+      colourBySequence(ap);
+    }
+  }
+
+  public StructureCommandsI getCommandGenerator()
+  {
+    return commandGenerator;
+  }
+
+  protected abstract ViewerType getViewerType();
+
+  /**
+   * Send a structure viewer command asynchronously in a new thread. If the
+   * progress message is not null, display this message while the command is
+   * executing.
+   * 
+   * @param command
+   * @param progressMsg
+   */
+  protected void sendAsynchronousCommand(String command, String progressMsg)
+  {
+    final JalviewStructureDisplayI theViewer = getViewer();
+    final long handle = progressMsg == null ? 0
+            : theViewer.startProgressBar(progressMsg);
+    SwingUtilities.invokeLater(new Runnable()
+    {
+      @Override
+      public void run()
+      {
+        try
+        {
+          executeCommand(command, false);
+        } finally
+        {
+          if (progressMsg != null)
+          {
+            theViewer.stopProgressBar(null, handle);
+          }
+        }
+      }
+    });
+
+  }
+
+  /**
+   * Builds a data structure which records mapped structure residues for each
+   * colour. From this we can easily generate the viewer commands for colour by
+   * sequence. Constructs and returns a map of {@code Color} to
+   * {@code AtomSpecModel}, where the atomspec model holds
+   * 
+   * <pre>
+   *   Model numbers
+   *     Chains
+   *       Residue positions
+   * </pre>
+   * 
+   * Ordering is by order of addition (for colours), natural ordering (for
+   * models and chains)
+   * 
+   * @param ssm
+   * @param files
+   * @param sequence
+   * @param sr
+   * @param viewPanel
+   * @return
+   */
+  protected Map<Object, AtomSpecModel> buildColoursMap(
+          StructureSelectionManager ssm, String[] files,
+          SequenceI[][] sequence, SequenceRenderer sr, AlignmentViewPanel viewPanel)
+  {
+    FeatureRenderer fr = viewPanel.getFeatureRenderer();
+    FeatureColourFinder finder = new FeatureColourFinder(fr);
+    AlignViewportI viewport = viewPanel.getAlignViewport();
+    HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
+    AlignmentI al = viewport.getAlignment();
+    Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<>();
+    Color lastColour = null;
+  
+    for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
+    {
+      final int modelNumber = pdbfnum + commandGenerator.getModelStartNo();
+      StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
+  
+      if (mapping == null || mapping.length < 1)
+      {
+        continue;
+      }
+  
+      int startPos = -1, lastPos = -1;
+      String lastChain = "";
+      for (int s = 0; s < sequence[pdbfnum].length; s++)
+      {
+        for (int sp, m = 0; m < mapping.length; m++)
+        {
+          final SequenceI seq = sequence[pdbfnum][s];
+          if (mapping[m].getSequence() == seq
+                  && (sp = al.findIndex(seq)) > -1)
+          {
+            SequenceI asp = al.getSequenceAt(sp);
+            for (int r = 0; r < asp.getLength(); r++)
+            {
+              // no mapping to gaps in sequence
+              if (Comparison.isGap(asp.getCharAt(r)))
+              {
+                continue;
+              }
+              int pos = mapping[m].getPDBResNum(asp.findPosition(r));
+  
+              if (pos < 1 || pos == lastPos)
+              {
+                continue;
+              }
+  
+              Color colour = sr.getResidueColour(seq, r, finder);
+  
+              /*
+               * darker colour for hidden regions
+               */
+              if (!cs.isVisible(r))
+              {
+                colour = Color.GRAY;
+              }
+  
+              final String chain = mapping[m].getChain();
+  
+              /*
+               * Just keep incrementing the end position for this colour range
+               * _unless_ colour, PDB model or chain has changed, or there is a
+               * gap in the mapped residue sequence
+               */
+              final boolean newColour = !colour.equals(lastColour);
+              final boolean nonContig = lastPos + 1 != pos;
+              final boolean newChain = !chain.equals(lastChain);
+              if (newColour || nonContig || newChain)
+              {
+                if (startPos != -1)
+                {
+                  addAtomSpecRange(colourMap, lastColour, modelNumber,
+                          startPos, lastPos, lastChain);
+                }
+                startPos = pos;
+              }
+              lastColour = colour;
+              lastPos = pos;
+              lastChain = chain;
+            }
+            // final colour range
+            if (lastColour != null)
+            {
+              addAtomSpecRange(colourMap, lastColour, modelNumber, startPos,
+                      lastPos, lastChain);
+            }
+            // break;
+          }
+        }
+      }
+    }
+    return colourMap;
+  }
+
+  /**
+   * Helper method to add one contiguous range to the AtomSpec model for the given
+   * value (creating the model if necessary). As used by Jalview, {@code value} is
+   * <ul>
+   * <li>a colour, when building a 'colour structure by sequence' command</li>
+   * <li>a feature value, when building a 'set Chimera attributes from features'
+   * command</li>
+   * </ul>
+   * 
+   * @param map
+   * @param value
+   * @param model
+   * @param startPos
+   * @param endPos
+   * @param chain
+   */
+  public static final void addAtomSpecRange(Map<Object, AtomSpecModel> map,
+          Object value,
+          int model, int startPos, int endPos, String chain)
+  {
+    /*
+     * Get/initialize map of data for the colour
+     */
+    AtomSpecModel atomSpec = map.get(value);
+    if (atomSpec == null)
+    {
+      atomSpec = new AtomSpecModel();
+      map.put(value, atomSpec);
+    }
+  
+    atomSpec.addRange(model, startPos, endPos, chain);
+  }
 }
index b19d606..8f97226 100644 (file)
@@ -34,6 +34,7 @@ import org.apache.http.NameValuePair;
 import org.apache.http.client.ClientProtocolException;
 import org.apache.http.client.HttpClient;
 import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.entity.mime.HttpMultipartMode;
 import org.apache.http.entity.mime.MultipartEntity;
@@ -167,4 +168,65 @@ public class HttpClientUtils
       return null;
     }
   }
+
+  /**
+   * do an HTTP GET with URL-Encoded parameters passed in the Query string
+   * 
+   * @param url
+   * @param vals
+   * @return Reader containing content, if any, or null if no entity returned.
+   * @throws IOException
+   * @throws ClientProtocolException
+   * @throws Exception
+   */
+  public static BufferedReader doHttpGet(String url,
+          List<NameValuePair> vals, int connectionTimeoutMs,
+          int readTimeoutMs) throws ClientProtocolException, IOException
+  {
+    // todo use HttpClient 4.3 or later and class RequestConfig
+    HttpParams params = new BasicHttpParams();
+    params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION,
+            HttpVersion.HTTP_1_1);
+    if (connectionTimeoutMs > 0)
+    {
+      HttpConnectionParams.setConnectionTimeout(params,
+              connectionTimeoutMs);
+    }
+    if (readTimeoutMs > 0)
+    {
+      HttpConnectionParams.setSoTimeout(params, readTimeoutMs);
+    }
+    boolean first = true;
+    for (NameValuePair param : vals)
+    {
+      if (first)
+      {
+        url += "?";
+      }
+      else
+      {
+        url += "&";
+      }
+      url += param.getName();
+      url += "=";
+      url += param.getValue();
+    }
+    HttpClient httpclient = new DefaultHttpClient(params);
+    HttpGet httpGet = new HttpGet(url);
+    // UrlEncodedFormEntity ue = new UrlEncodedFormEntity(vals, "UTF-8");
+    // httpGet.setEntity(ue);
+    HttpResponse response = httpclient.execute(httpGet);
+    HttpEntity resEntity = response.getEntity();
+  
+    if (resEntity != null)
+    {
+      BufferedReader r = new BufferedReader(
+              new InputStreamReader(resEntity.getContent()));
+      return r;
+    }
+    else
+    {
+      return null;
+    }
+  }
 }
index e42b54f..d1a9df6 100644 (file)
@@ -32,11 +32,15 @@ import jalview.gui.AlignFrame;
 import jalview.gui.JvOptionPane;
 import jalview.gui.SequenceRenderer;
 import jalview.schemes.JalviewColourScheme;
+import jalview.structure.AtomSpecModel;
+import jalview.structure.StructureCommandsI;
 import jalview.structure.StructureMapping;
-import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
 
+import java.awt.Color;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
 
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
@@ -52,24 +56,6 @@ public class JmolCommandsTest
   }
 
   @Test(groups = { "Functional" })
-  public void testGetColourBySequenceCommand_noFeatures()
-  {
-    SequenceI seq1 = new Sequence("seq1", "MHRSQTRALK");
-    SequenceI seq2 = new Sequence("seq2", "MRLEITQSGD");
-    AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
-    AlignFrame af = new AlignFrame(al, 800, 500);
-    SequenceRenderer sr = new SequenceRenderer(af.getViewport());
-    SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
-    String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
-    StructureSelectionManager ssm = new StructureSelectionManager();
-
-    // need some mappings!
-
-    StructureMappingcommandSet[] commands = JmolCommands
-            .getColourBySequenceCommand(ssm, files, seqs, sr, af.alignPanel);
-  }
-
-  @Test(groups = { "Functional" })
   public void testGetColourBySequenceCommands_hiddenColumns()
   {
     /*
@@ -91,11 +77,11 @@ public class JmolCommandsTest
     SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
     String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
     StructureSelectionManager ssm = new StructureSelectionManager();
-  
+
     /*
      * map residues 1-10 to residues 21-30 (atoms 105-150) in structures
      */
-    HashMap<Integer, int[]> map = new HashMap<Integer, int[]>();
+    HashMap<Integer, int[]> map = new HashMap<>();
     for (int pos = 1; pos <= seq1.getLength(); pos++)
     {
       map.put(pos, new int[] { 20 + pos, 5 * (20 + pos) });
@@ -106,37 +92,125 @@ public class JmolCommandsTest
     StructureMapping sm2 = new StructureMapping(seq2, "seq2.pdb", "pdb2",
             "B", map, null);
     ssm.addStructureMapping(sm2);
-  
-    StructureMappingcommandSet[] commands = JmolCommands
-            .getColourBySequenceCommand(ssm, files, seqs, sr, af.alignPanel);
+
+    String[] commands = new JmolCommands().colourBySequence(ssm, files,
+            seqs, sr, af.alignPanel);
     assertEquals(commands.length, 2);
-    assertEquals(commands[0].commands.length, 1);
 
-    String chainACommand = commands[0].commands[0];
+    String chainACommand = commands[0];
     // M colour is #82827d == (130, 130, 125) (see strand.html help page)
-    assertTrue(chainACommand
-            .contains("select 21:A/1.1;color[130,130,125]")); // first one
+    assertTrue(
+            chainACommand.contains("select 21:A/1.1;color[130,130,125]")); // first
+                                                                           // one
     // H colour is #60609f == (96, 96, 159)
     assertTrue(chainACommand.contains(";select 22:A/1.1;color[96,96,159]"));
     // hidden columns are Gray (128, 128, 128)
     assertTrue(chainACommand
             .contains(";select 23-25:A/1.1;color[128,128,128]"));
     // S and G are both coloured #4949b6 == (73, 73, 182)
-    assertTrue(chainACommand
-            .contains(";select 26-30:A/1.1;color[73,73,182]"));
+    assertTrue(
+            chainACommand.contains(";select 26-30:A/1.1;color[73,73,182]"));
 
-    String chainBCommand = commands[1].commands[0];
+    String chainBCommand = commands[1];
     // M colour is #82827d == (130, 130, 125)
-    assertTrue(chainBCommand
-            .contains("select 21:B/2.1;color[130,130,125]"));
+    assertTrue(
+            chainBCommand.contains("select 21:B/2.1;color[130,130,125]"));
     // V colour is #ffff00 == (255, 255, 0)
-    assertTrue(chainBCommand
-.contains(";select 22:B/2.1;color[255,255,0]"));
+    assertTrue(chainBCommand.contains(";select 22:B/2.1;color[255,255,0]"));
     // hidden columns are Gray (128, 128, 128)
     assertTrue(chainBCommand
             .contains(";select 23-25:B/2.1;color[128,128,128]"));
     // S and G are both coloured #4949b6 == (73, 73, 182)
-    assertTrue(chainBCommand
-            .contains(";select 26-30:B/2.1;color[73,73,182]"));
+    assertTrue(
+            chainBCommand.contains(";select 26-30:B/2.1;color[73,73,182]"));
+  }
+
+  @Test(groups = "Functional")
+  public void testGetAtomSpec()
+  {
+    StructureCommandsI testee = new JmolCommands();
+    AtomSpecModel model = new AtomSpecModel();
+    assertEquals(testee.getAtomSpec(model, false), "");
+    model.addRange(1, 2, 4, "A");
+    assertEquals(testee.getAtomSpec(model, false), "2-4:A/1.1");
+    model.addRange(1, 8, 8, "A");
+    assertEquals(testee.getAtomSpec(model, false), "2-4:A/1.1|8:A/1.1");
+    model.addRange(1, 5, 7, "B");
+    assertEquals(testee.getAtomSpec(model, false),
+            "2-4:A/1.1|8:A/1.1|5-7:B/1.1");
+    model.addRange(1, 3, 5, "A");
+    assertEquals(testee.getAtomSpec(model, false),
+            "2-5:A/1.1|8:A/1.1|5-7:B/1.1");
+    model.addRange(2, 1, 4, "B");
+    assertEquals(testee.getAtomSpec(model, false),
+            "2-5:A/1.1|8:A/1.1|5-7:B/1.1|1-4:B/2.1");
+    model.addRange(2, 5, 9, "C");
+    assertEquals(testee.getAtomSpec(model, false),
+            "2-5:A/1.1|8:A/1.1|5-7:B/1.1|1-4:B/2.1|5-9:C/2.1");
+    model.addRange(1, 8, 10, "B");
+    assertEquals(testee.getAtomSpec(model, false),
+            "2-5:A/1.1|8:A/1.1|5-10:B/1.1|1-4:B/2.1|5-9:C/2.1");
+    model.addRange(1, 8, 9, "B");
+    assertEquals(testee.getAtomSpec(model, false),
+            "2-5:A/1.1|8:A/1.1|5-10:B/1.1|1-4:B/2.1|5-9:C/2.1");
+    model.addRange(2, 3, 10, "C"); // subsumes 5-9
+    assertEquals(testee.getAtomSpec(model, false),
+            "2-5:A/1.1|8:A/1.1|5-10:B/1.1|1-4:B/2.1|3-10:C/2.1");
+    model.addRange(5, 25, 35, " ");
+    assertEquals(testee.getAtomSpec(model, false),
+            "2-5:A/1.1|8:A/1.1|5-10:B/1.1|1-4:B/2.1|3-10:C/2.1|25-35:/5.1");
+  
+  }
+
+  @Test(groups = { "Functional" })
+  public void testColourBySequence()
+  {
+    Map<Object, AtomSpecModel> map = new LinkedHashMap<>();
+    JmolCommands.addAtomSpecRange(map, Color.blue, 1, 2, 5, "A");
+    JmolCommands.addAtomSpecRange(map, Color.blue, 1, 7, 7, "B");
+    JmolCommands.addAtomSpecRange(map, Color.blue, 1, 9, 23, "A");
+    JmolCommands.addAtomSpecRange(map, Color.blue, 2, 1, 1, "A");
+    JmolCommands.addAtomSpecRange(map, Color.blue, 2, 4, 7, "B");
+    JmolCommands.addAtomSpecRange(map, Color.yellow, 2, 8, 8, "A");
+    JmolCommands.addAtomSpecRange(map, Color.yellow, 2, 3, 5, "A");
+    JmolCommands.addAtomSpecRange(map, Color.red, 1, 3, 5, "A");
+    JmolCommands.addAtomSpecRange(map, Color.red, 1, 6, 9, "A");
+
+    // Colours should appear in the Jmol command in the order in which
+    // they were added; within colour, by model, by chain, ranges in start order
+    String[] commands = new JmolCommands().colourBySequence(map);
+    assertEquals(commands.length, 1);
+    String expected = "select 2-5:A/1.1|9-23:A/1.1|7:B/1.1|1:A/2.1|4-7:B/2.1;color[0,0,255]; "
+            + "select 3-5:A/2.1|8:A/2.1;color[255,255,0]; "
+            + "select 3-9:A/1.1;color[255,0,0]";
+    assertEquals(commands[0], expected);
+  }
+
+  @Test(groups = { "Functional" })
+  public void testSuperposeStructures()
+  {
+    StructureCommandsI testee = new JmolCommands();
+    AtomSpecModel ref = new AtomSpecModel();
+    ref.addRange(1, 12, 14, "A");
+    ref.addRange(1, 18, 18, "B");
+    ref.addRange(1, 22, 23, "B");
+    AtomSpecModel toAlign = new AtomSpecModel();
+    toAlign.addRange(2, 15, 17, "B");
+    toAlign.addRange(2, 20, 21, "B");
+    toAlign.addRange(2, 22, 22, "C");
+    String command = testee.superposeStructures(ref, toAlign);
+    String refSpec = "12-14:A/1.1|18:B/1.1|22-23:B/1.1";
+    String toAlignSpec = "15-17:B/2.1|20-21:B/2.1|22:C/2.1";
+    String expected = String.format(
+            "compare {2.1} {1.1} SUBSET {(*.CA | *.P) and conformation=1} ATOMS {%s}{%s} ROTATE TRANSLATE ;select %s|%s;cartoons",
+            toAlignSpec, refSpec, toAlignSpec, refSpec);
+    assertEquals(command, expected);
+  }
+
+  @Test(groups = "Functional")
+  public void testGetModelStartNo()
+  {
+    StructureCommandsI testee = new JmolCommands();
+    assertEquals(testee.getModelStartNo(), 1);
   }
 }
diff --git a/test/jalview/ext/rbvi/chimera/AtomSpecModelTest.java b/test/jalview/ext/rbvi/chimera/AtomSpecModelTest.java
deleted file mode 100644 (file)
index 63d5e4e..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-package jalview.ext.rbvi.chimera;
-
-import static org.testng.Assert.assertEquals;
-
-import org.testng.annotations.Test;
-
-public class AtomSpecModelTest
-{
-  @Test(groups = "Functional")
-  public void testGetAtomSpec()
-  {
-    AtomSpecModel model = new AtomSpecModel();
-    assertEquals(model.getAtomSpec(), "");
-    model.addRange(1, 2, 4, "A");
-    assertEquals(model.getAtomSpec(), "#1:2-4.A");
-    model.addRange(1, 8, 8, "A");
-    assertEquals(model.getAtomSpec(), "#1:2-4.A,8.A");
-    model.addRange(1, 5, 7, "B");
-    assertEquals(model.getAtomSpec(), "#1:2-4.A,8.A,5-7.B");
-    model.addRange(1, 3, 5, "A");
-    assertEquals(model.getAtomSpec(), "#1:2-5.A,8.A,5-7.B");
-    model.addRange(0, 1, 4, "B");
-    assertEquals(model.getAtomSpec(), "#0:1-4.B|#1:2-5.A,8.A,5-7.B");
-    model.addRange(0, 5, 9, "C");
-    assertEquals(model.getAtomSpec(), "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-7.B");
-    model.addRange(1, 8, 10, "B");
-    assertEquals(model.getAtomSpec(), "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-10.B");
-    model.addRange(1, 8, 9, "B");
-    assertEquals(model.getAtomSpec(), "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-10.B");
-    model.addRange(0, 3, 10, "C"); // subsumes 5-9
-    assertEquals(model.getAtomSpec(), "#0:1-4.B,3-10.C|#1:2-5.A,8.A,5-10.B");
-    model.addRange(5, 25, 35, " "); // empty chain code - e.g. from homology
-                                    // modelling
-    assertEquals(model.getAtomSpec(),
-            "#0:1-4.B,3-10.C|#1:2-5.A,8.A,5-10.B|#5:25-35.");
-
-  }
-
-}
index 06a09df..5f0e84b 100644 (file)
@@ -23,43 +23,24 @@ package jalview.ext.rbvi.chimera;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 
-import jalview.datamodel.Alignment;
-import jalview.datamodel.AlignmentI;
-import jalview.datamodel.ColumnSelection;
-import jalview.datamodel.Sequence;
-import jalview.datamodel.SequenceI;
-import jalview.gui.AlignFrame;
-import jalview.gui.JvOptionPane;
-import jalview.gui.SequenceRenderer;
-import jalview.schemes.JalviewColourScheme;
-import jalview.structure.StructureMapping;
-import jalview.structure.StructureMappingcommandSet;
-import jalview.structure.StructureSelectionManager;
+import jalview.structure.AtomSpecModel;
+import jalview.structure.StructureCommandsI;
 
 import java.awt.Color;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
-import java.util.List;
 import java.util.Map;
 
-import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 public class ChimeraCommandsTest
 {
 
-  @BeforeClass(alwaysRun = true)
-  public void setUpJvOptionPane()
-  {
-    JvOptionPane.setInteractiveMode(false);
-    JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
-  }
-
   @Test(groups = { "Functional" })
-  public void testBuildColourCommands()
+  public void testColourBySequence()
   {
 
-    Map<Object, AtomSpecModel> map = new LinkedHashMap<Object, AtomSpecModel>();
+    Map<Object, AtomSpecModel> map = new LinkedHashMap<>();
     ChimeraCommands.addAtomSpecRange(map, Color.blue, 0, 2, 5, "A");
     ChimeraCommands.addAtomSpecRange(map, Color.blue, 0, 7, 7, "B");
     ChimeraCommands.addAtomSpecRange(map, Color.blue, 0, 9, 23, "A");
@@ -72,20 +53,21 @@ public class ChimeraCommandsTest
 
     // Colours should appear in the Chimera command in the order in which
     // they were added; within colour, by model, by chain, ranges in start order
-    String command = ChimeraCommands.buildColourCommands(map).get(0);
+    String[] commands = new ChimeraCommands().colourBySequence(map);
+    assertEquals(commands.length, 1);
     assertEquals(
-            command,
+            commands[0],
             "color #0000ff #0:2-5.A,9-23.A,7.B|#1:1.A,4-7.B; color #ffff00 #1:3-5.A,8.A; color #ff0000 #0:3-9.A");
   }
 
   @Test(groups = { "Functional" })
-  public void testBuildSetAttributeCommands()
+  public void testSetAttributes()
   {
     /*
      * make a map of { featureType, {featureValue, {residue range specification } } }
      */
-    Map<String, Map<Object, AtomSpecModel>> featuresMap = new LinkedHashMap<String, Map<Object, AtomSpecModel>>();
-    Map<Object, AtomSpecModel> featureValues = new HashMap<Object, AtomSpecModel>();
+    Map<String, Map<Object, AtomSpecModel>> featuresMap = new LinkedHashMap<>();
+    Map<Object, AtomSpecModel> featureValues = new HashMap<>();
     
     /*
      * start with just one feature/value...
@@ -93,42 +75,45 @@ public class ChimeraCommandsTest
     featuresMap.put("chain", featureValues);
     ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 8, 20, "A");
   
-    List<String> commands = ChimeraCommands
-            .buildSetAttributeCommands(featuresMap);
-    assertEquals(1, commands.size());
+    ChimeraCommands commandGenerator = new ChimeraCommands();
+    String[] commands = commandGenerator.setAttributes(featuresMap);
+    assertEquals(1, commands.length);
 
     /*
      * feature name gets a jv_ namespace prefix
      * feature value is quoted in case it contains spaces
      */
-    assertEquals(commands.get(0), "setattr r jv_chain 'X' #0:8-20.A");
+    assertEquals(commands[0], "setattr res jv_chain 'X' #0:8-20.A");
 
     // add same feature value, overlapping range
     ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 3, 9, "A");
     // same feature value, contiguous range
     ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 21, 25, "A");
-    commands = ChimeraCommands.buildSetAttributeCommands(featuresMap);
-    assertEquals(1, commands.size());
-    assertEquals(commands.get(0), "setattr r jv_chain 'X' #0:3-25.A");
+    commands = commandGenerator.setAttributes(featuresMap);
+    assertEquals(1, commands.length);
+    assertEquals(commands[0], "setattr res jv_chain 'X' #0:3-25.A");
 
     // same feature value and model, different chain
     ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 21, 25, "B");
     // same feature value and chain, different model
     ChimeraCommands.addAtomSpecRange(featureValues, "X", 1, 26, 30, "A");
-    commands = ChimeraCommands.buildSetAttributeCommands(featuresMap);
-    assertEquals(1, commands.size());
-    assertEquals(commands.get(0),
-            "setattr r jv_chain 'X' #0:3-25.A,21-25.B|#1:26-30.A");
+    commands = commandGenerator.setAttributes(featuresMap);
+    assertEquals(1, commands.length);
+    String expected1 = "setattr res jv_chain 'X' #0:3-25.A,21-25.B|#1:26-30.A";
+    assertEquals(commands[0],
+            expected1);
 
     // same feature, different value
     ChimeraCommands.addAtomSpecRange(featureValues, "Y", 0, 40, 50, "A");
-    commands = ChimeraCommands.buildSetAttributeCommands(featuresMap);
-    assertEquals(2, commands.size());
+    commands = commandGenerator.setAttributes(featuresMap);
+    assertEquals(2, commands.length);
     // commands are ordered by feature type but not by value
-    // so use contains to test for the expected command:
-    assertTrue(commands
-            .contains("setattr r jv_chain 'X' #0:3-25.A,21-25.B|#1:26-30.A"));
-    assertTrue(commands.contains("setattr r jv_chain 'Y' #0:40-50.A"));
+    // so test for the expected command in either order
+    assertTrue(
+            commands[0].equals(expected1) || commands[1].equals(expected1));
+    String expected2 = "setattr res jv_chain 'Y' #0:40-50.A";
+    assertTrue(
+            commands[0].equals(expected2) || commands[1].equals(expected2));
 
     featuresMap.clear();
     featureValues.clear();
@@ -138,9 +123,10 @@ public class ChimeraCommandsTest
             "A");
     // feature names are sanitised to change non-alphanumeric to underscore
     // feature values are sanitised to encode single quote characters
-    commands = ChimeraCommands.buildSetAttributeCommands(featuresMap);
-    assertTrue(commands
-            .contains("setattr r jv_side_chain_binding_ '<html>metal <a href=\"http:a.b.c/x\"> &#39;ion!' #0:7-15.A"));
+    commands = commandGenerator.setAttributes(featuresMap);
+    String expected3 = "setattr res jv_side_chain_binding_ '<html>metal <a href=\"http:a.b.c/x\"> &#39;ion!' #0:7-15.A";
+    assertTrue(
+            commands[0].equals(expected3) || commands[1].equals(expected3));
   }
 
   /**
@@ -163,58 +149,154 @@ public class ChimeraCommandsTest
             "jv_helixColor_");
   }
 
+  @Test(groups = "Functional")
+  public void testGetAtomSpec()
+  {
+    StructureCommandsI testee = new ChimeraCommands();
+    AtomSpecModel model = new AtomSpecModel();
+    assertEquals(testee.getAtomSpec(model, false), "");
+    model.addRange(1, 2, 4, "A");
+    assertEquals(testee.getAtomSpec(model, false), "#1:2-4.A");
+    model.addRange(1, 8, 8, "A");
+    assertEquals(testee.getAtomSpec(model, false), "#1:2-4.A,8.A");
+    model.addRange(1, 5, 7, "B");
+    assertEquals(testee.getAtomSpec(model, false), "#1:2-4.A,8.A,5-7.B");
+    model.addRange(1, 3, 5, "A");
+    assertEquals(testee.getAtomSpec(model, false), "#1:2-5.A,8.A,5-7.B");
+    model.addRange(0, 1, 4, "B");
+    assertEquals(testee.getAtomSpec(model, false),
+            "#0:1-4.B|#1:2-5.A,8.A,5-7.B");
+    model.addRange(0, 5, 9, "C");
+    assertEquals(testee.getAtomSpec(model, false),
+            "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-7.B");
+    model.addRange(1, 8, 10, "B");
+    assertEquals(testee.getAtomSpec(model, false),
+            "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-10.B");
+    model.addRange(1, 8, 9, "B");
+    assertEquals(testee.getAtomSpec(model, false),
+            "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-10.B");
+    model.addRange(0, 3, 10, "C"); // subsumes 5-9
+    assertEquals(testee.getAtomSpec(model, false),
+            "#0:1-4.B,3-10.C|#1:2-5.A,8.A,5-10.B");
+    model.addRange(5, 25, 35, " "); // empty chain code - e.g. from homology
+                                    // modelling
+    assertEquals(testee.getAtomSpec(model, false),
+            "#0:1-4.B,3-10.C|#1:2-5.A,8.A,5-10.B|#5:25-35.");
+
+  }
+
   @Test(groups = { "Functional" })
-  public void testGetColourBySequenceCommands_hiddenColumns()
+  public void testSuperposeStructures()
   {
-    /*
-     * load these sequences, coloured by Strand propensity,
-     * with columns 2-4 hidden
-     */
-    SequenceI seq1 = new Sequence("seq1", "MHRSQSSSGG");
-    SequenceI seq2 = new Sequence("seq2", "MVRSNGGSSS");
-    AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
-    AlignFrame af = new AlignFrame(al, 800, 500);
-    af.changeColour_actionPerformed(JalviewColourScheme.Strand.toString());
-    ColumnSelection cs = new ColumnSelection();
-    cs.addElement(2);
-    cs.addElement(3);
-    cs.addElement(4);
-    af.getViewport().setColumnSelection(cs);
-    af.hideSelColumns_actionPerformed(null);
-    SequenceRenderer sr = new SequenceRenderer(af.getViewport());
-    SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
-    String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
-    StructureSelectionManager ssm = new StructureSelectionManager();
+    StructureCommandsI testee = new ChimeraCommands();
+    AtomSpecModel ref = new AtomSpecModel();
+    ref.addRange(1, 12, 14, "A");
+    ref.addRange(1, 18, 18, "B");
+    ref.addRange(1, 22, 23, "B");
+    AtomSpecModel toAlign = new AtomSpecModel();
+    toAlign.addRange(2, 15, 17, "B");
+    toAlign.addRange(2, 20, 21, "B");
+    toAlign.addRange(2, 22, 22, "C");
+    String command = testee.superposeStructures(ref, toAlign);
+    String refSpec = "#1:12-14.A,18.B,22-23.B@CA|P&~@.B-Z&~@.2-9";
+    String toAlignSpec = "#2:15-17.B,20-21.B,22.C@CA|P&~@.B-Z&~@.2-9";
+    String expected = String.format(
+            "match %s %s;~display all; chain @CA|P; ribbon %s|%s; focus",
+            refSpec, toAlignSpec, refSpec, toAlignSpec);
+    assertEquals(command, expected);
+  }
 
-    /*
-     * map residues 1-10 to residues 21-30 (atoms 105-150) in structures
-     */
-    HashMap<Integer, int[]> map = new HashMap<Integer, int[]>();
-    for (int pos = 1; pos <= seq1.getLength(); pos++)
-    {
-      map.put(pos, new int[] { 20 + pos, 5 * (20 + pos) });
-    }
-    StructureMapping sm1 = new StructureMapping(seq1, "seq1.pdb", "pdb1",
-            "A", map, null);
-    ssm.addStructureMapping(sm1);
-    StructureMapping sm2 = new StructureMapping(seq2, "seq2.pdb", "pdb2",
-            "B", map, null);
-    ssm.addStructureMapping(sm2);
-
-    StructureMappingcommandSet[] commands = ChimeraCommands
-            .getColourBySequenceCommand(ssm, files, seqs, sr, af.alignPanel);
-    assertEquals(1, commands.length);
-    assertEquals(1, commands[0].commands.length);
-    String theCommand = commands[0].commands[0];
-    // M colour is #82827d (see strand.html help page)
-    assertTrue(theCommand.contains("color #82827d #0:21.A|#1:21.B"));
-    // H colour is #60609f
-    assertTrue(theCommand.contains("color #60609f #0:22.A"));
-    // V colour is #ffff00
-    assertTrue(theCommand.contains("color #ffff00 #1:22.B"));
-    // hidden columns are Gray (128, 128, 128)
-    assertTrue(theCommand.contains("color #808080 #0:23-25.A|#1:23-25.B"));
-    // S and G are both coloured #4949b6
-    assertTrue(theCommand.contains("color #4949b6 #0:26-30.A|#1:26-30.B"));
+  @Test(groups = "Functional")
+  public void testGetAtomSpec_alphaOnly()
+  {
+    StructureCommandsI testee = new ChimeraCommands();
+    AtomSpecModel model = new AtomSpecModel();
+    assertEquals(testee.getAtomSpec(model, true), "");
+    model.addRange(1, 2, 4, "A");
+    assertEquals(testee.getAtomSpec(model, true),
+            "#1:2-4.A@CA|P&~@.B-Z&~@.2-9");
+    model.addRange(1, 8, 8, "A");
+    assertEquals(testee.getAtomSpec(model, true),
+            "#1:2-4.A,8.A@CA|P&~@.B-Z&~@.2-9");
+    model.addRange(1, 5, 7, "B");
+    assertEquals(testee.getAtomSpec(model, true),
+            "#1:2-4.A,8.A,5-7.B@CA|P&~@.B-Z&~@.2-9");
+    model.addRange(1, 3, 5, "A");
+    assertEquals(testee.getAtomSpec(model, true),
+            "#1:2-5.A,8.A,5-7.B@CA|P&~@.B-Z&~@.2-9");
+    model.addRange(0, 1, 4, "B");
+    assertEquals(testee.getAtomSpec(model, true),
+            "#0:1-4.B@CA|P&~@.B-Z&~@.2-9|#1:2-5.A,8.A,5-7.B@CA|P&~@.B-Z&~@.2-9");
+    model.addRange(0, 5, 9, "C");
+    assertEquals(testee.getAtomSpec(model, true),
+            "#0:1-4.B,5-9.C@CA|P&~@.B-Z&~@.2-9|#1:2-5.A,8.A,5-7.B@CA|P&~@.B-Z&~@.2-9");
+    model.addRange(1, 8, 10, "B");
+    assertEquals(testee.getAtomSpec(model, true),
+            "#0:1-4.B,5-9.C@CA|P&~@.B-Z&~@.2-9|#1:2-5.A,8.A,5-10.B@CA|P&~@.B-Z&~@.2-9");
+    model.addRange(1, 8, 9, "B");
+    assertEquals(testee.getAtomSpec(model, true),
+            "#0:1-4.B,5-9.C@CA|P&~@.B-Z&~@.2-9|#1:2-5.A,8.A,5-10.B@CA|P&~@.B-Z&~@.2-9");
+    model.addRange(0, 3, 10, "C"); // subsumes 5-9
+    assertEquals(testee.getAtomSpec(model, true),
+            "#0:1-4.B,3-10.C@CA|P&~@.B-Z&~@.2-9|#1:2-5.A,8.A,5-10.B@CA|P&~@.B-Z&~@.2-9");
+    model.addRange(5, 25, 35, " "); // empty chain code
+    assertEquals(testee.getAtomSpec(model, true),
+            "#0:1-4.B,3-10.C@CA|P&~@.B-Z&~@.2-9|#1:2-5.A,8.A,5-10.B@CA|P&~@.B-Z&~@.2-9|#5:25-35.@CA|P&~@.B-Z&~@.2-9");
+  
+  }
+
+  @Test(groups = "Functional")
+  public void testGetModelStartNo()
+  {
+    StructureCommandsI testee = new ChimeraCommands();
+    assertEquals(testee.getModelStartNo(), 0);
+  }
+
+  @Test(groups = "Functional")
+  public void testGetResidueSpec()
+  {
+    ChimeraCommands testee = new ChimeraCommands();
+    assertEquals(testee.getResidueSpec("ALA"), "::ALA");
+  }
+
+  @Test(groups = "Functional")
+  public void testShowBackbone()
+  {
+    ChimeraCommands testee = new ChimeraCommands();
+    assertEquals(testee.showBackbone(), "~display all;chain @CA|P");
+  }
+
+  @Test(groups = "Functional")
+  public void testOpenCommandFile()
+  {
+    ChimeraCommands testee = new ChimeraCommands();
+    assertEquals(testee.openCommandFile("nowhere"), "open cmd:nowhere");
+  }
+
+  @Test(groups = "Functional")
+  public void testSaveSession()
+  {
+    ChimeraCommands testee = new ChimeraCommands();
+    assertEquals(testee.saveSession("somewhere"), "save somewhere");
+  }
+
+  @Test(groups = "Functional")
+  public void testGetColourCommand()
+  {
+    ChimeraCommands testee = new ChimeraCommands();
+    assertEquals(testee.getColourCommand("something", Color.MAGENTA),
+            "color #ff00ff something");
+  }
+
+  @Test(groups = "Functional")
+  public void testSetAttribute()
+  {
+    ChimeraCommands testee = new ChimeraCommands();
+    AtomSpecModel model = new AtomSpecModel();
+    model.addRange(1, 89, 92, "A");
+    model.addRange(2, 12, 20, "B");
+    model.addRange(2, 8, 9, "B");
+    assertEquals(testee.setAttribute("phi", "27.3", model),
+            "setattr res phi '27.3' #1:89-92.A|#2:8-9.B,12-20.B");
   }
 }
index 99394dc..b07a622 100644 (file)
@@ -47,7 +47,7 @@ public class ChimeraConnect
     final StructureManager structureManager = new StructureManager(true);
     ChimeraManager cm = new ChimeraManager(structureManager);
     assertTrue("Couldn't launch chimera",
-            cm.launchChimera(StructureManager.getChimeraPaths()));
+            cm.launchChimera(StructureManager.getChimeraPaths(false)));
     assertTrue(cm.isChimeraLaunched()); // Chimera process is alive
     // int n=0;
     // not sure of the point of this is unless the tester is loading models
diff --git a/test/jalview/ext/rbvi/chimera/ChimeraXCommandsTest.java b/test/jalview/ext/rbvi/chimera/ChimeraXCommandsTest.java
new file mode 100644 (file)
index 0000000..ddda2c0
--- /dev/null
@@ -0,0 +1,301 @@
+/*
+ * 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.ext.rbvi.chimera;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+import jalview.structure.AtomSpecModel;
+import jalview.structure.StructureCommandsI;
+
+import java.awt.Color;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.testng.annotations.Test;
+
+public class ChimeraXCommandsTest
+{
+  @Test(groups = { "Functional" })
+  public void testColourByCharge()
+  {
+    String cmd = new ChimeraXCommands().colourByCharge();
+    assertEquals(cmd,
+            "color white;color :ASP,GLU red;color :LYS,ARG blue;color :CYS yellow");
+  }
+
+  @Test(groups = { "Functional" })
+  public void testColourByChain()
+  {
+    String cmd = new ChimeraXCommands().colourByChain();
+    assertEquals(cmd, "rainbow chain");
+  }
+
+  @Test(groups = { "Functional" })
+  public void testFocusView()
+  {
+    String cmd = new ChimeraXCommands().focusView();
+    assertEquals(cmd, "view");
+  }
+
+  @Test(groups = { "Functional" })
+  public void testColourBySequence()
+  {
+    Map<Object, AtomSpecModel> map = new LinkedHashMap<>();
+    ChimeraCommands.addAtomSpecRange(map, Color.blue, 1, 2, 5, "A");
+    ChimeraCommands.addAtomSpecRange(map, Color.blue, 1, 7, 7, "B");
+    ChimeraCommands.addAtomSpecRange(map, Color.blue, 1, 9, 23, "A");
+    ChimeraCommands.addAtomSpecRange(map, Color.blue, 2, 1, 1, "A");
+    ChimeraCommands.addAtomSpecRange(map, Color.blue, 2, 4, 7, "B");
+    ChimeraCommands.addAtomSpecRange(map, Color.yellow, 2, 8, 8, "A");
+    ChimeraCommands.addAtomSpecRange(map, Color.yellow, 2, 3, 5, "A");
+    ChimeraCommands.addAtomSpecRange(map, Color.red, 1, 3, 5, "A");
+    ChimeraCommands.addAtomSpecRange(map, Color.red, 1, 6, 9, "A");
+
+    // Colours should appear in the Chimera command in the order in which
+    // they were added; within colour, by model, by chain, ranges in start order
+    String[] commands = new ChimeraXCommands().colourBySequence(map);
+    assertEquals(commands.length, 1);
+    assertEquals(
+            commands[0],
+            "color #1/A:2-5,9-23/B:7|#2/A:1/B:4-7 #0000ff; color #2/A:3-5,8 #ffff00; color #1/A:3-9 #ff0000");
+  }
+
+  @Test(groups = { "Functional" })
+  public void testSetAttributes()
+  {
+    /*
+     * make a map of { featureType, {featureValue, {residue range specification } } }
+     */
+    Map<String, Map<Object, AtomSpecModel>> featuresMap = new LinkedHashMap<>();
+    Map<Object, AtomSpecModel> featureValues = new HashMap<>();
+    
+    /*
+     * start with just one feature/value...
+     */
+    featuresMap.put("chain", featureValues);
+    ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 8, 20, "A");
+  
+    ChimeraXCommands commandGenerator = new ChimeraXCommands();
+    String[] commands = commandGenerator.setAttributes(featuresMap);
+    assertEquals(commands.length, 1);
+
+    /*
+     * feature name gets a jv_ namespace prefix
+     * feature value is quoted in case it contains spaces
+     */
+    assertEquals(commands[0],
+            "setattr #0/A:8-20 res jv_chain 'X' create true");
+
+    // add same feature value, overlapping range
+    ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 3, 9, "A");
+    // same feature value, contiguous range
+    ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 21, 25, "A");
+    commands = commandGenerator.setAttributes(featuresMap);
+    assertEquals(commands.length, 1);
+    assertEquals(commands[0],
+            "setattr #0/A:3-25 res jv_chain 'X' create true");
+
+    // same feature value and model, different chain
+    ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 21, 25, "B");
+    // same feature value and chain, different model
+    ChimeraCommands.addAtomSpecRange(featureValues, "X", 1, 26, 30, "A");
+    commands = commandGenerator.setAttributes(featuresMap);
+    assertEquals(commands.length, 1);
+    String expected1 = "setattr #0/A:3-25/B:21-25|#1/A:26-30 res jv_chain 'X' create true";
+    assertEquals(commands[0], expected1);
+
+    // same feature, different value
+    ChimeraCommands.addAtomSpecRange(featureValues, "Y", 0, 40, 50, "A");
+    commands = commandGenerator.setAttributes(featuresMap);
+    assertEquals(2, commands.length);
+    // commands are ordered by feature type but not by value
+    // so test for the expected command in either order
+    assertTrue(
+            commands[0].equals(expected1) || commands[1].equals(expected1));
+    String expected2 = "setattr #0/A:40-50 res jv_chain 'Y' create true";
+    assertTrue(
+            commands[0].equals(expected2) || commands[1].equals(expected2));
+
+    featuresMap.clear();
+    featureValues.clear();
+    featuresMap.put("side-chain binding!", featureValues);
+    ChimeraCommands.addAtomSpecRange(featureValues,
+            "<html>metal <a href=\"http:a.b.c/x\"> 'ion!", 0, 7, 15,
+            "A");
+    // feature names are sanitised to change non-alphanumeric to underscore
+    // feature values are sanitised to encode single quote characters
+    commands = commandGenerator.setAttributes(featuresMap);
+    String expected3 = "setattr #0/A:7-15 res jv_side_chain_binding_ '<html>metal <a href=\"http:a.b.c/x\"> &#39;ion!' create true";
+    assertTrue(
+            commands[0].equals(expected3) || commands[1].equals(expected3));
+  }
+
+  @Test(groups = { "Functional" })
+  public void testSuperposeStructures()
+  {
+    StructureCommandsI testee = new ChimeraXCommands();
+    AtomSpecModel ref = new AtomSpecModel();
+    ref.addRange(1, 12, 14, "A");
+    ref.addRange(1, 18, 18, "B");
+    ref.addRange(1, 22, 23, "B");
+    AtomSpecModel toAlign = new AtomSpecModel();
+    toAlign.addRange(2, 15, 17, "B");
+    toAlign.addRange(2, 20, 21, "B");
+    toAlign.addRange(2, 22, 22, "C");
+    String command = testee.superposeStructures(ref, toAlign);
+    String refSpec = "#1/A:12-14/B:18,22-23";
+    String toAlignSpec = "#2/B:15-17,20-21/C:22";
+
+    /*
+     * superposition arguments include AlphaCarbon restriction,
+     * ribbon command does not
+     */
+    String expected = String.format(
+            "align %s@CA|P toAtoms %s@CA|P; ribbon %s|%s; view",
+            refSpec, toAlignSpec, refSpec, toAlignSpec);
+    assertEquals(command, expected);
+  }
+
+  @Test(groups = "Functional")
+  public void testGetAtomSpec()
+  {
+    StructureCommandsI testee = new ChimeraXCommands();
+    AtomSpecModel model = new AtomSpecModel();
+    assertEquals(testee.getAtomSpec(model, false), "");
+    model.addRange(1, 2, 4, "A");
+    assertEquals(testee.getAtomSpec(model, false), "#1/A:2-4");
+    model.addRange(1, 8, 8, "A");
+    assertEquals(testee.getAtomSpec(model, false), "#1/A:2-4,8");
+    model.addRange(1, 5, 7, "B");
+    assertEquals(testee.getAtomSpec(model, false), "#1/A:2-4,8/B:5-7");
+    model.addRange(1, 3, 5, "A");
+    assertEquals(testee.getAtomSpec(model, false), "#1/A:2-5,8/B:5-7");
+    model.addRange(0, 1, 4, "B");
+    assertEquals(testee.getAtomSpec(model, false),
+            "#0/B:1-4|#1/A:2-5,8/B:5-7");
+    model.addRange(0, 5, 9, "C");
+    assertEquals(testee.getAtomSpec(model, false),
+            "#0/B:1-4/C:5-9|#1/A:2-5,8/B:5-7");
+    model.addRange(1, 8, 10, "B");
+    assertEquals(testee.getAtomSpec(model, false),
+            "#0/B:1-4/C:5-9|#1/A:2-5,8/B:5-10");
+    model.addRange(1, 8, 9, "B");
+    assertEquals(testee.getAtomSpec(model, false),
+            "#0/B:1-4/C:5-9|#1/A:2-5,8/B:5-10");
+    model.addRange(0, 3, 10, "C"); // subsumes 5-9
+    assertEquals(testee.getAtomSpec(model, false),
+            "#0/B:1-4/C:3-10|#1/A:2-5,8/B:5-10");
+    model.addRange(5, 25, 35, " ");
+    assertEquals(testee.getAtomSpec(model, false),
+            "#0/B:1-4/C:3-10|#1/A:2-5,8/B:5-10|#5/:25-35");
+  }
+
+  @Test(groups = "Functional")
+  public void testGetAtomSpec_alphaOnly()
+  {
+    StructureCommandsI testee = new ChimeraXCommands();
+    AtomSpecModel model = new AtomSpecModel();
+    assertEquals(testee.getAtomSpec(model, true), "");
+    model.addRange(1, 2, 4, "A");
+    assertEquals(testee.getAtomSpec(model, true), "#1/A:2-4@CA|P");
+    model.addRange(1, 8, 8, "A");
+    assertEquals(testee.getAtomSpec(model, true), "#1/A:2-4,8@CA|P");
+    model.addRange(1, 5, 7, "B");
+    assertEquals(testee.getAtomSpec(model, true), "#1/A:2-4,8/B:5-7@CA|P");
+    model.addRange(1, 3, 5, "A");
+    assertEquals(testee.getAtomSpec(model, true), "#1/A:2-5,8/B:5-7@CA|P");
+    model.addRange(0, 1, 4, "B");
+    assertEquals(testee.getAtomSpec(model, true),
+            "#0/B:1-4@CA|P|#1/A:2-5,8/B:5-7@CA|P");
+    model.addRange(0, 5, 9, "C");
+    assertEquals(testee.getAtomSpec(model, true),
+            "#0/B:1-4/C:5-9@CA|P|#1/A:2-5,8/B:5-7@CA|P");
+    model.addRange(1, 8, 10, "B");
+    assertEquals(testee.getAtomSpec(model, true),
+            "#0/B:1-4/C:5-9@CA|P|#1/A:2-5,8/B:5-10@CA|P");
+    model.addRange(1, 8, 9, "B");
+    assertEquals(testee.getAtomSpec(model, true),
+            "#0/B:1-4/C:5-9@CA|P|#1/A:2-5,8/B:5-10@CA|P");
+    model.addRange(0, 3, 10, "C"); // subsumes 5-9
+    assertEquals(testee.getAtomSpec(model, true),
+            "#0/B:1-4/C:3-10@CA|P|#1/A:2-5,8/B:5-10@CA|P");
+    model.addRange(5, 25, 35, " "); // empty chain code
+    assertEquals(testee.getAtomSpec(model, true),
+            "#0/B:1-4/C:3-10@CA|P|#1/A:2-5,8/B:5-10@CA|P|#5/:25-35@CA|P");
+  }
+
+  @Test(groups = "Functional")
+  public void testGetModelStartNo()
+  {
+    StructureCommandsI testee = new ChimeraXCommands();
+    assertEquals(testee.getModelStartNo(), 1);
+  }
+
+  @Test(groups = "Functional")
+  public void testGetResidueSpec()
+  {
+    ChimeraCommands testee = new ChimeraXCommands();
+    assertEquals(testee.getResidueSpec("ALA"), ":ALA");
+  }
+
+  @Test(groups = "Functional")
+  public void testShowBackbone()
+  {
+    ChimeraCommands testee = new ChimeraXCommands();
+    assertEquals(testee.showBackbone(), "~display all;show @CA|P pbonds");
+  }
+
+  @Test(groups = "Functional")
+  public void testOpenCommandFile()
+  {
+    ChimeraCommands testee = new ChimeraXCommands();
+    assertEquals(testee.openCommandFile("nowhere"), "open nowhere");
+  }
+
+  @Test(groups = "Functional")
+  public void testSaveSession()
+  {
+    ChimeraCommands testee = new ChimeraXCommands();
+    assertEquals(testee.saveSession("somewhere"), "save session somewhere");
+  }
+
+  @Test(groups = "Functional")
+  public void testGetColourCommand()
+  {
+    ChimeraCommands testee = new ChimeraXCommands();
+    assertEquals(testee.getColourCommand("something", Color.MAGENTA),
+            "color something #ff00ff");
+  }
+
+  @Test(groups = "Functional")
+  public void testSetAttribute()
+  {
+    ChimeraCommands testee = new ChimeraXCommands();
+    AtomSpecModel model = new AtomSpecModel();
+    model.addRange(1, 89, 92, "A");
+    model.addRange(2, 12, 20, "B");
+    model.addRange(2, 8, 9, "B");
+    assertEquals(testee.setAttribute("phi", "27.3", model),
+            "setattr #1/A:89-92|#2/B:8-9,12-20 res phi '27.3' create true");
+  }
+}
index 734f7eb..93ed555 100644 (file)
@@ -290,7 +290,7 @@ public class JalviewChimeraView
     /*
      * ask Chimera for its residue attribute names
      */
-    List<String> reply = binding.sendChimeraCommand("list resattr", true);
+    List<String> reply = binding.executeCommand("list resattr", true);
     // prefixed and sanitised attribute names for Jalview features:
     assertTrue(reply.contains("resattr jv_domain"));
     assertTrue(reply.contains("resattr jv_metal_ion_binding_site"));
@@ -306,7 +306,7 @@ public class JalviewChimeraView
      * ask Chimera for residues with an attribute
      * 91 and 96 on sequence --> residues 40 and 45 on chains A and B
      */
-    reply = binding.sendChimeraCommand(
+    reply = binding.executeCommand(
             "list resi att jv_metal_ion_binding_site", true);
     assertEquals(reply.size(), 4);
     assertTrue(reply
@@ -322,7 +322,7 @@ public class JalviewChimeraView
      * check attributes with score values
      * sequence positions 62 and 65 --> residues 11 and 14 on chains A and B
      */
-    reply = binding.sendChimeraCommand("list resi att jv_kd", true);
+    reply = binding.executeCommand("list resi att jv_kd", true);
     assertEquals(reply.size(), 4);
     assertTrue(reply.contains("residue id #0:11.A jv_kd -2.1 index 11"));
     assertTrue(reply.contains("residue id #0:14.A jv_kd 3.6 index 14"));
@@ -332,7 +332,7 @@ public class JalviewChimeraView
     /*
      * list residues with positive kd score 
      */
-    reply = binding.sendChimeraCommand(
+    reply = binding.executeCommand(
             "list resi spec :*/jv_kd>0 attr jv_kd", true);
     assertEquals(reply.size(), 2);
     assertTrue(reply.contains("residue id #0:14.A jv_kd 3.6 index 14"));
@@ -402,8 +402,8 @@ public class JalviewChimeraView
     assertEquals(binding.getPdbCount(), 1);
   
     /*
-     * 'perform' menu action to copy visible features to
-     * attributes in Chimera
+     * 'perform' menu action to copy Chimera attributes
+     * to features in Jalview
      */
     // TODO rename and pull up method to binding interface
     // once functionality is added for Jmol as well
@@ -440,14 +440,9 @@ public class JalviewChimeraView
     binding.copyStructureAttributesToFeatures("phi", af.getViewport()
             .getAlignPanel());
     fr.setVisible("phi");
-    List<SequenceFeature> fs = fer2Arath.getFeatures().findFeatures(54, 54);
-    assertEquals(fs.size(), 3);
-    /*
-     * order of returned features is not guaranteed
-     */
-    assertTrue("RESNUM".equals(fs.get(0).getType())
-            || "RESNUM".equals(fs.get(1).getType())
-            || "RESNUM".equals(fs.get(2).getType()));
+    List<SequenceFeature> fs = fer2Arath.getFeatures().findFeatures(54, 54,
+            "phi");
+    assertEquals(fs.size(), 2);
     assertTrue(fs.contains(new SequenceFeature("phi", "A", 54, 54,
             -131.0713f, "Chimera")));
     assertTrue(fs.contains(new SequenceFeature("phi", "B", 54, 54,
@@ -473,11 +468,11 @@ public class JalviewChimeraView
           int res, String featureType)
   {
     String where = "at position " + res;
-    List<SequenceFeature> fs = seq.getFeatures().findFeatures(res, res);
+    List<SequenceFeature> fs = seq.getFeatures().findFeatures(res, res,
+            featureType);
 
-    assertEquals(fs.size(), 2, where);
-    assertEquals(fs.get(0).getType(), "RESNUM", where);
-    SequenceFeature sf = fs.get(1);
+    assertEquals(fs.size(), 1, where);
+    SequenceFeature sf = fs.get(0);
     assertEquals(sf.getType(), featureType, where);
     assertEquals(sf.getFeatureGroup(), "Chimera", where);
     assertEquals(sf.getDescription(), "True", where);
diff --git a/test/jalview/structure/AtomSpecModelTest.java b/test/jalview/structure/AtomSpecModelTest.java
new file mode 100644 (file)
index 0000000..8c6ead7
--- /dev/null
@@ -0,0 +1,51 @@
+package jalview.structure;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+
+import org.testng.annotations.Test;
+
+public class AtomSpecModelTest
+{
+  @Test(groups="Functional")
+  public void testGetRanges()
+  {
+    AtomSpecModel model = new AtomSpecModel();
+    assertFalse(model.getModels().iterator().hasNext());
+    List<int[]> ranges = model.getRanges(1, "A");
+    assertTrue(ranges.isEmpty());
+
+    model.addRange(1, 12, 14, "A");
+    assertTrue(model.getRanges(1, "B").isEmpty());
+    assertTrue(model.getRanges(2, "A").isEmpty());
+    ranges = model.getRanges(1, "A");
+    assertEquals(ranges.size(), 1);
+    int[] range = ranges.get(0);
+    assertEquals(range[0], 12);
+    assertEquals(range[1], 14);
+
+    /*
+     * add some ranges; they should be coalesced and
+     * ordered when retrieved
+     */
+    model.addRange(1, 25, 25, "A");
+    model.addRange(1, 20, 24, "A");
+    model.addRange(1, 6, 8, "A");
+    model.addRange(1, 13, 18, "A");
+    model.addRange(1, 5, 6, "A");
+    ranges = model.getRanges(1, "A");
+    assertEquals(ranges.size(), 3);
+    range = ranges.get(0);
+    assertEquals(range[0], 5);
+    assertEquals(range[1], 8);
+    range = ranges.get(1);
+    assertEquals(range[0], 12);
+    assertEquals(range[1], 18);
+    range = ranges.get(2);
+    assertEquals(range[0], 20);
+    assertEquals(range[1], 25);
+  }
+}
index ea53131..ff6e6cb 100644 (file)
@@ -9,23 +9,23 @@ import org.testng.annotations.Test;
 public class AtomSpecTest
 {
   @Test
-  public void testFromChimeraAtomSpec()
+  public void testFromChimeraAtomSpec_chimera()
   {
-    AtomSpec as = AtomSpec.fromChimeraAtomspec("#1:12.B");
+    AtomSpec as = AtomSpec.fromChimeraAtomspec("#1:12.B", false);
     assertEquals(as.getModelNumber(), 1);
     assertEquals(as.getPdbResNum(), 12);
     assertEquals(as.getChain(), "B");
     assertNull(as.getPdbFile());
 
     // no model - default to zero
-    as = AtomSpec.fromChimeraAtomspec(":13.C");
+    as = AtomSpec.fromChimeraAtomspec(":13.C", false);
     assertEquals(as.getModelNumber(), 0);
     assertEquals(as.getPdbResNum(), 13);
     assertEquals(as.getChain(), "C");
     assertNull(as.getPdbFile());
 
     // model.submodel
-    as = AtomSpec.fromChimeraAtomspec("#3.2:15");
+    as = AtomSpec.fromChimeraAtomspec("#3.2:15", false);
     assertEquals(as.getModelNumber(), 3);
     assertEquals(as.getPdbResNum(), 15);
     assertEquals(as.getChain(), "");
@@ -34,7 +34,7 @@ public class AtomSpecTest
     String spec = "3:12.B";
     try
     {
-      as = AtomSpec.fromChimeraAtomspec(spec);
+      as = AtomSpec.fromChimeraAtomspec(spec, false);
       fail("Expected exception for " + spec);
     } catch (IllegalArgumentException e)
     {
@@ -44,7 +44,7 @@ public class AtomSpecTest
     spec = "#3:12-14.B";
     try
     {
-      as = AtomSpec.fromChimeraAtomspec(spec);
+      as = AtomSpec.fromChimeraAtomspec(spec, false);
       fail("Expected exception for " + spec);
     } catch (IllegalArgumentException e)
     {
@@ -54,7 +54,7 @@ public class AtomSpecTest
     spec = "";
     try
     {
-      as = AtomSpec.fromChimeraAtomspec(spec);
+      as = AtomSpec.fromChimeraAtomspec(spec, false);
       fail("Expected exception for " + spec);
     } catch (IllegalArgumentException e)
     {
@@ -64,7 +64,71 @@ public class AtomSpecTest
     spec = null;
     try
     {
-      as = AtomSpec.fromChimeraAtomspec(spec);
+      as = AtomSpec.fromChimeraAtomspec(spec, false);
+      fail("Expected exception for " + spec);
+    } catch (NullPointerException e)
+    {
+      // ok
+    }
+  }
+
+  @Test
+  public void testFromChimeraAtomSpec_chimeraX()
+  {
+    AtomSpec as = AtomSpec.fromChimeraAtomspec("#1/B:12", true);
+    assertEquals(as.getModelNumber(), 1);
+    assertEquals(as.getPdbResNum(), 12);
+    assertEquals(as.getChain(), "B");
+    assertNull(as.getPdbFile());
+  
+    // no model - default to zero
+    as = AtomSpec.fromChimeraAtomspec("/C:13", true);
+    assertEquals(as.getModelNumber(), 0);
+    assertEquals(as.getPdbResNum(), 13);
+    assertEquals(as.getChain(), "C");
+    assertNull(as.getPdbFile());
+  
+    // model.submodel
+    as = AtomSpec.fromChimeraAtomspec("#3.2/:15", true);
+    assertEquals(as.getModelNumber(), 3);
+    assertEquals(as.getPdbResNum(), 15);
+    assertEquals(as.getChain(), "");
+    assertNull(as.getPdbFile());
+  
+    String spec = "3:12.B";
+    try
+    {
+      as = AtomSpec.fromChimeraAtomspec(spec, true);
+      fail("Expected exception for " + spec);
+    } catch (IllegalArgumentException e)
+    {
+      // ok
+    }
+  
+    spec = "#3:12-14.B";
+    try
+    {
+      as = AtomSpec.fromChimeraAtomspec(spec, true);
+      fail("Expected exception for " + spec);
+    } catch (IllegalArgumentException e)
+    {
+      // ok
+    }
+  
+    spec = "";
+    try
+    {
+      as = AtomSpec.fromChimeraAtomspec(spec, true);
+      fail("Expected exception for " + spec);
+    } catch (IllegalArgumentException e)
+    {
+      // ok
+    }
+  
+    spec = null;
+    try
+    {
+      as = AtomSpec.fromChimeraAtomspec(spec, true);
       fail("Expected exception for " + spec);
     } catch (NullPointerException e)
     {
index 286be1b..b86e91f 100644 (file)
@@ -124,10 +124,10 @@ public class StructureSelectionManagerTest extends Jalview2xmlBase
     acf3.addMap(new Sequence("s3", "ttt"), new Sequence("p3", "p"),
             new MapList(new int[] { 1, 3 }, new int[] { 1, 1 }, 1, 1));
 
-    List<AlignedCodonFrame> set1 = new ArrayList<AlignedCodonFrame>();
+    List<AlignedCodonFrame> set1 = new ArrayList<>();
     set1.add(acf1);
     set1.add(acf2);
-    List<AlignedCodonFrame> set2 = new ArrayList<AlignedCodonFrame>();
+    List<AlignedCodonFrame> set2 = new ArrayList<>();
     set2.add(acf2);
     set2.add(acf3);
 
@@ -218,7 +218,7 @@ public class StructureSelectionManagerTest extends Jalview2xmlBase
     assertEquals(1, pmap.getSeqs().size());
     assertEquals("4IM2|A", pmap.getSeqs().get(0).getName());
 
-    List<int[]> structuremap1 = new ArrayList(
+    List<int[]> structuremap1 = new ArrayList<>(
             sm.getMapping(P4IM2_MISSING)[0]
                     .getPDBResNumRanges(seq.getStart(), seq.getEnd()));
 
@@ -313,8 +313,7 @@ public class StructureSelectionManagerTest extends Jalview2xmlBase
     // positional mapping to atoms for color by structure is still wrong, even
     // though panel looks correct.
 
-    StructureMappingcommandSet smcr[] = JmolCommands
-            .getColourBySequenceCommand(apssm,
+    String[] smcr = new JmolCommands().colourBySequence(apssm,
             new String[]
             { pdbe.getFile() },
             new SequenceI[][]
@@ -322,12 +321,10 @@ public class StructureSelectionManagerTest extends Jalview2xmlBase
                     new SequenceRenderer(alf.alignPanel.getAlignViewport()),
                     alf.alignPanel);
     // Expected - all residues are white
-    for (StructureMappingcommandSet smm : smcr)
+    for (String c : smcr)
     {
-      for (String c : smm.commands)
-      {
-        System.out.println(c);
-      }
+      assertTrue(c.contains("color[255,255,255]"));
+      System.out.println(c);
     }
   }
 
index af02d5e..f901cbd 100644 (file)
  */
 package jalview.structures.models;
 
+import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
-import static org.testng.AssertJUnit.assertEquals;
-import static org.testng.AssertJUnit.assertTrue;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
 
 import jalview.api.AlignmentViewPanel;
-import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.PDBEntry;
 import jalview.datamodel.PDBEntry.Type;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceI;
+import jalview.ext.rbvi.chimera.ChimeraCommands;
+import jalview.gui.AlignFrame;
 import jalview.gui.JvOptionPane;
+import jalview.gui.StructureViewer.ViewerType;
 import jalview.io.DataSourceType;
 import jalview.io.FileFormats;
-import jalview.schemes.ColourSchemeI;
+import jalview.schemes.JalviewColourScheme;
 import jalview.structure.AtomSpec;
-import jalview.structure.StructureMappingcommandSet;
+import jalview.structure.AtomSpecModel;
+import jalview.structure.StructureCommandsI.SuperposeData;
+import jalview.structure.StructureMapping;
 import jalview.structure.StructureSelectionManager;
-import jalview.structures.models.AAStructureBindingModel.SuperposeData;
 
 import java.awt.Color;
 import java.io.IOException;
 import java.util.Arrays;
 import java.util.BitSet;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import junit.extensions.PA;
+
 /**
  * Unit tests for non-abstract methods of abstract base class
  * 
@@ -139,110 +147,55 @@ public class AAStructureBindingModelTest
       @Override
       public void updateColours(Object source)
       {
-        // TODO Auto-generated method stub
-        
       }
       
       @Override
       public void releaseReferences(Object svl)
       {
-        // TODO Auto-generated method stub
-        
       }
       
       @Override
       public String[] getStructureFiles()
       {
-        // TODO Auto-generated method stub
-        return null;
-      }
-      
-      @Override
-      public String superposeStructures(AlignmentI[] alignments,
-              int[] structureIndices, HiddenColumns[] hiddenCols)
-      {
-        // TODO Auto-generated method stub
         return null;
       }
       
       @Override
-      public void setJalviewColourScheme(ColourSchemeI cs)
-      {
-        // TODO Auto-generated method stub
-        
-      }
-      
-      @Override
-      public void setBackgroundColour(Color col)
-      {
-        // TODO Auto-generated method stub
-        
-      }
-      
-      @Override
       public void highlightAtoms(List<AtomSpec> atoms)
       {
-        // TODO Auto-generated method stub
-        
       }
       
       @Override
       public SequenceRenderer getSequenceRenderer(AlignmentViewPanel alignment)
       {
-        // TODO Auto-generated method stub
         return null;
       }
-      
+
       @Override
-      public FeatureRenderer getFeatureRenderer(AlignmentViewPanel alignment)
+      protected List<String> executeCommand(String command,
+              boolean getReply)
       {
-        // TODO Auto-generated method stub
         return null;
       }
-      
+
       @Override
-      protected StructureMappingcommandSet[] getColourBySequenceCommands(
-              String[] files, SequenceRenderer sr, AlignmentViewPanel avp)
+      protected int getModelNoForFile(String chainId)
       {
-        // TODO Auto-generated method stub
-        return null;
+        return 0;
       }
-      
+
       @Override
-      public List<String> getChainNames()
+      protected ViewerType getViewerType()
       {
-        // TODO Auto-generated method stub
         return null;
       }
-      
-      @Override
-      protected void colourBySequence(
-              StructureMappingcommandSet[] colourBySequenceCommands)
-      {
-        // TODO Auto-generated method stub
-        
-      }
-      
-      @Override
-      public void colourByCharge()
-      {
-        // TODO Auto-generated method stub
-        
-      }
-      
-      @Override
-      public void colourByChain()
-      {
-        // TODO Auto-generated method stub
-        
-      }
     };
     String[][] chains = binder.getChains();
     assertFalse(chains == null || chains[0] == null,
             "No chains discovered by binding");
-    assertEquals(2, chains[0].length);
-    assertEquals("A", chains[0][0]);
-    assertEquals("B", chains[0][1]);
+    assertEquals(chains[0].length, 2);
+    assertEquals(chains[0][0], "A");
+    assertEquals(chains[0][1], "B");
   }
   AAStructureBindingModel testee;
 
@@ -281,7 +234,22 @@ public class AAStructureBindingModelTest
     ssm.setMapping(new SequenceI[] { seq3 }, null, PDB_3,
             DataSourceType.PASTE, null);
 
-    testee = new AAStructureBindingModel(ssm, pdbFiles, seqs, null)
+    testee = newBindingModel(pdbFiles, seqs, ssm);
+  }
+
+  /**
+   * A helper method to construct the test target object
+   * 
+   * @param pdbFiles
+   * @param seqs
+   * @param ssm
+   */
+  protected AAStructureBindingModel newBindingModel(PDBEntry[] pdbFiles,
+          SequenceI[][] seqs,
+          StructureSelectionManager ssm)
+  {
+    AAStructureBindingModel model = new AAStructureBindingModel(ssm,
+            pdbFiles, seqs, null)
     {
       @Override
       public String[] getStructureFiles()
@@ -305,36 +273,6 @@ public class AAStructureBindingModelTest
       }
 
       @Override
-      public List<String> getChainNames()
-      {
-        return null;
-      }
-
-      @Override
-      public void setJalviewColourScheme(ColourSchemeI cs)
-      {
-      }
-
-      @Override
-      public String superposeStructures(AlignmentI[] als, int[] alm,
-              HiddenColumns[] alc)
-      {
-        return null;
-      }
-
-      @Override
-      public void setBackgroundColour(Color col)
-      {
-      }
-
-      @Override
-      protected StructureMappingcommandSet[] getColourBySequenceCommands(
-              String[] files, SequenceRenderer sr, AlignmentViewPanel avp)
-      {
-        return null;
-      }
-
-      @Override
       public SequenceRenderer getSequenceRenderer(
               AlignmentViewPanel alignment)
       {
@@ -342,28 +280,26 @@ public class AAStructureBindingModelTest
       }
 
       @Override
-      protected void colourBySequence(
-              StructureMappingcommandSet[] colourBySequenceCommands)
-      {
-      }
-
-      @Override
-      public void colourByChain()
+      protected List<String> executeCommand(String command,
+              boolean getReply)
       {
+        return null;
       }
 
       @Override
-      public void colourByCharge()
+      protected int getModelNoForFile(String chainId)
       {
+        return 0;
       }
 
       @Override
-      public FeatureRenderer getFeatureRenderer(
-              AlignmentViewPanel alignment)
+      protected ViewerType getViewerType()
       {
         return null;
       }
     };
+    PA.setValue(model, "commandGenerator", new ChimeraCommands());
+    return model;
   }
 
   /**
@@ -379,7 +315,7 @@ public class AAStructureBindingModelTest
     SuperposeData[] structs = new SuperposeData[testee.getStructureFiles().length];
     for (int i = 0; i < structs.length; i++)
     {
-      structs[i] = testee.new SuperposeData(al.getWidth());
+      structs[i] = new SuperposeData(al.getWidth(), 0);
     }
     /*
      * initialise BitSet of 'superposable columns' to true (would be false for
@@ -394,7 +330,7 @@ public class AAStructureBindingModelTest
     int refStructure = testee
             .findSuperposableResidues(al, matched, structs);
 
-    assertEquals(0, refStructure);
+    assertEquals(refStructure, 0);
 
     /*
      * only ungapped, structure-mapped columns are superposable
@@ -406,18 +342,19 @@ public class AAStructureBindingModelTest
     assertTrue(matched.get(4));
     assertTrue(matched.get(5)); // gap in second sequence
 
-    assertEquals("1YCS", structs[0].pdbId);
-    assertEquals("3A6S", structs[1].pdbId);
-    assertEquals("1OOT", structs[2].pdbId);
-    assertEquals("A", structs[0].chain); // ? struct has chains A _and_ B
-    assertEquals("B", structs[1].chain);
-    assertEquals("A", structs[2].chain);
+    assertEquals(structs[0].pdbId, "1YCS");
+    assertEquals(structs[1].pdbId, "3A6S");
+    assertEquals(structs[2].pdbId, "1OOT");
+    assertEquals(structs[0].chain, "A"); // ? struct has chains A _and_ B
+    assertEquals(structs[1].chain, "B");
+    assertEquals(structs[2].chain, "A");
     // the 0's for unsuperposable positions propagate down the columns:
-    assertEquals("[0, 97, 98, 99, 100, 102]",
-            Arrays.toString(structs[0].pdbResNo));
-    assertEquals("[0, 2, 0, 3, 4, 5]", Arrays.toString(structs[1].pdbResNo));
-    assertEquals("[0, 8, 0, 0, 10, 12]",
-            Arrays.toString(structs[2].pdbResNo));
+    assertEquals(Arrays.toString(structs[0].pdbResNo),
+            "[0, 97, 98, 99, 100, 102]");
+    assertEquals(Arrays.toString(structs[1].pdbResNo),
+            "[0, 2, 0, 3, 4, 5]");
+    assertEquals(Arrays.toString(structs[2].pdbResNo),
+            "[0, 8, 0, 0, 10, 12]");
   }
 
   @Test(groups = { "Functional" })
@@ -426,7 +363,7 @@ public class AAStructureBindingModelTest
     SuperposeData[] structs = new SuperposeData[al.getHeight()];
     for (int i = 0; i < structs.length; i++)
     {
-      structs[i] = testee.new SuperposeData(al.getWidth());
+      structs[i] = new SuperposeData(al.getWidth(), 0);
     }
     /*
      * initialise BitSet of 'superposable columns' to true (would be false for
@@ -444,7 +381,7 @@ public class AAStructureBindingModelTest
     int refStructure = testee
             .findSuperposableResidues(al, matched, structs);
 
-    assertEquals(0, refStructure);
+    assertEquals(refStructure, 0);
 
     // only ungapped, structure-mapped columns are not superposable
     assertFalse(matched.get(0));
@@ -454,4 +391,100 @@ public class AAStructureBindingModelTest
     assertFalse(matched.get(4)); // superposable, but hidden, column
     assertTrue(matched.get(5));
   }
-}
+
+  @Test(groups = { "Functional" })
+  public void testBuildColoursMap()
+  {
+    /*
+     * load these sequences, coloured by Strand propensity,
+     * with columns 2-4 hidden
+     */
+    SequenceI seq1 = new Sequence("seq1", "MHRSQSSSGG");
+    SequenceI seq2 = new Sequence("seq2", "MVRSNGGSSS");
+    AlignmentI al = new Alignment(new SequenceI[] { seq1, seq2 });
+    AlignFrame af = new AlignFrame(al, 800, 500);
+    af.changeColour_actionPerformed(JalviewColourScheme.Strand.toString());
+    ColumnSelection cs = new ColumnSelection();
+    cs.addElement(2);
+    cs.addElement(3);
+    cs.addElement(4);
+    af.getViewport().setColumnSelection(cs);
+    af.hideSelColumns_actionPerformed(null);
+    SequenceRenderer sr = new jalview.gui.SequenceRenderer(
+            af.getViewport());
+    SequenceI[][] seqs = new SequenceI[][] { { seq1 }, { seq2 } };
+    String[] files = new String[] { "seq1.pdb", "seq2.pdb" };
+    PDBEntry[] pdbFiles = new PDBEntry[2];
+    pdbFiles[0] = new PDBEntry("PDB1", "A", Type.PDB, "INLINEPDB1");
+    pdbFiles[1] = new PDBEntry("PDB2", "B", Type.PDB, "INLINEPDB2");
+    StructureSelectionManager ssm = new StructureSelectionManager();
+  
+    /*
+     * map residues 1-10 to residues 21-30 (atoms 105-150) in structures
+     */
+    HashMap<Integer, int[]> map = new HashMap<>();
+    for (int pos = 1; pos <= seq1.getLength(); pos++)
+    {
+      map.put(pos, new int[] { 20 + pos, 5 * (20 + pos) });
+    }
+    StructureMapping sm1 = new StructureMapping(seq1, "seq1.pdb", "pdb1",
+            "A", map, null);
+    ssm.addStructureMapping(sm1);
+    StructureMapping sm2 = new StructureMapping(seq2, "seq2.pdb", "pdb2",
+            "B", map, null);
+    ssm.addStructureMapping(sm2);
+
+    AAStructureBindingModel binding = newBindingModel(pdbFiles, seqs, ssm);
+
+    /*
+     * method under test builds a map of structures residues by colour
+     * verify the map holds what it should
+     */
+    Map<Object, AtomSpecModel> colours = binding.buildColoursMap(ssm, files,
+            seqs, sr, af.alignPanel);
+    ChimeraCommands helper = new ChimeraCommands();
+    
+    /*
+     * M colour is #82827d (see strand.html help page)
+     * sequence residue 1 mapped to structure residue 21
+     */
+    Color mColor = new Color(0x82827d);
+    AtomSpecModel atomSpec = colours.get(mColor);
+    assertNotNull(atomSpec);
+    assertEquals(helper.getAtomSpec(atomSpec, false), "#0:21.A|#1:21.B");
+
+    /*
+     * H colour is #60609f, seq1.2 mapped to structure 0 residue 22
+     */
+    Color hColor = new Color(0x60609f);
+    atomSpec = colours.get(hColor);
+    assertNotNull(atomSpec);
+    assertEquals(helper.getAtomSpec(atomSpec, false), "#0:22.A");
+
+    /*
+     * V colour is #ffff00, seq2.2 mapped to structure 1 residue 22
+     */
+    Color vColor = new Color(0xffff00);
+    atomSpec = colours.get(vColor);
+    assertNotNull(atomSpec);
+    assertEquals(helper.getAtomSpec(atomSpec, false), "#1:22.B");
+
+    /*
+     * hidden columns are Gray (128, 128, 128)
+     * sequence positions 3-5 mapped to structure residues 23-25
+     */
+    Color gray = new Color(128, 128, 128);
+    atomSpec = colours.get(gray);
+    assertNotNull(atomSpec);
+    assertEquals(helper.getAtomSpec(atomSpec, false), "#0:23-25.A|#1:23-25.B");
+
+    /*
+     * S and G are both coloured #4949b6, structure residues 26-30
+     */
+    Color sgColour = new Color(0x4949b6);
+    atomSpec = colours.get(sgColour);
+    assertNotNull(atomSpec);
+    assertEquals(helper.getAtomSpec(atomSpec, false),
+            "#0:26-30.A|#1:26-30.B");
+  }
+}
\ No newline at end of file