JAL-2422 pull-up refactoring of structure commands (continued)
[jalview.git] / src / jalview / structures / models / AAStructureBindingModel.java
index 9f4cea0..8c2dc46 100644 (file)
@@ -21,6 +21,7 @@
 package jalview.structures.models;
 
 import jalview.api.AlignmentViewPanel;
+import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
 import jalview.api.StructureSelectionManagerProvider;
 import jalview.api.structures.JalviewStructureDisplayI;
@@ -30,10 +31,11 @@ import jalview.datamodel.PDBEntry;
 import jalview.datamodel.SequenceI;
 import jalview.io.DataSourceType;
 import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ResidueProperties;
 import jalview.structure.AtomSpec;
+import jalview.structure.StructureCommandsI;
 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 +44,11 @@ import java.awt.Color;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+
+import javax.swing.SwingUtilities;
 
 /**
  * 
@@ -57,10 +63,33 @@ public abstract class AAStructureBindingModel
         extends SequenceStructureBindingModel
         implements StructureListener, StructureSelectionManagerProvider
 {
+  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
    */
@@ -134,6 +163,8 @@ public abstract class AAStructureBindingModel
   {
     this.ssm = ssm;
     this.sequence = seqs;
+    chainNames = new ArrayList<>();
+    chainFile = new HashMap<>();
   }
 
   /**
@@ -142,20 +173,70 @@ public abstract class AAStructureBindingModel
    * @param ssm
    * @param pdbentry
    * @param sequenceIs
-   * @param chains
    * @param protocol
    */
   public AAStructureBindingModel(StructureSelectionManager ssm,
           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;
+    resolveChains();
   }
 
+  private boolean resolveChains()
+  {
+    /**
+     * final count of chain mappings discovered
+     */
+    int chainmaps = 0;
+    // JBPNote: JAL-2693 - this should be a list of chain mappings per
+    // [pdbentry][sequence]
+    String[][] newchains = new String[pdbEntry.length][];
+    int pe = 0;
+    for (PDBEntry pdb : pdbEntry)
+    {
+      SequenceI[] seqsForPdb = sequence[pe];
+      if (seqsForPdb != null)
+      {
+        newchains[pe] = new String[seqsForPdb.length];
+        int se = 0;
+        for (SequenceI asq : seqsForPdb)
+        {
+          String chain = (chains != null && chains[pe] != null)
+                  ? chains[pe][se]
+                  : null;
+          SequenceI sq = (asq.getDatasetSequence() == null) ? asq
+                  : asq.getDatasetSequence();
+          if (sq.getAllPDBEntries() != null)
+          {
+            for (PDBEntry pdbentry : sq.getAllPDBEntries())
+            {
+              if (pdb.getFile() != null && pdbentry.getFile() != null
+                      && pdb.getFile().equals(pdbentry.getFile()))
+              {
+                String chaincode = pdbentry.getChainCode();
+                if (chaincode != null && chaincode.length() > 0)
+                {
+                  chain = chaincode;
+                  chainmaps++;
+                  break;
+                }
+              }
+            }
+          }
+          newchains[pe][se] = chain;
+          se++;
+        }
+        pe++;
+      }
+    }
+
+    chains = newchains;
+    return chainmaps > 0;
+  }
   public StructureSelectionManager getSsm()
   {
     return ssm;
@@ -304,8 +385,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()][]);
@@ -374,8 +455,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));
@@ -681,11 +762,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)
@@ -694,10 +779,13 @@ 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
@@ -718,11 +806,6 @@ public abstract class AAStructureBindingModel
   public abstract String superposeStructures(AlignmentI[] alignments,
           int[] structureIndices, HiddenColumns[] hiddenCols);
 
-  public abstract void setBackgroundColour(Color col);
-
-  protected abstract StructureMappingcommandSet[] getColourBySequenceCommands(
-          String[] files, SequenceRenderer sr, AlignmentViewPanel avp);
-
   /**
    * returns the current sequenceRenderer that should be used to colour the
    * structures
@@ -734,12 +817,174 @@ 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 abstract void colourByChain();
+  public void setBackgroundColour(Color col)
+  {
+    String cmd = commandGenerator.setBackgroundColour(col);
+    executeCommand(cmd, false, null);
+  }
 
-  public abstract void colourByCharge();
+  /**
+   * 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);
+          }
+        }
+      }
+    });
+  }
+
+  protected abstract List<String> executeCommand(String command,
+          boolean getReply);
+
+  protected List<String> executeCommands(boolean getReply,
+          String... commands)
+  {
+    List<String> response = null;
+    for (String cmd : commands)
+    {
+      response = executeCommand(cmd, getReply);
+    }
+    return response;
+  }
 
   /**
    * colour any structures associated with sequences in the given alignment
@@ -760,16 +1005,107 @@ public abstract class AAStructureBindingModel
 
     SequenceRenderer sr = getSequenceRenderer(alignmentv);
 
-    StructureMappingcommandSet[] colourBySequenceCommands = getColourBySequenceCommands(
-            files, sr, alignmentv);
-    colourBySequence(colourBySequenceCommands);
+    String[] colourBySequenceCommands = commandGenerator
+            .colourBySequence(getSsm(), files, getSequence(), sr,
+                    alignmentv);
+    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 ?
+
+    /*
+     * 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);
+  }
 }