JAL-1609 wip on Chimera colour by sequence
[jalview.git] / src / jalview / ext / rbvi / chimera / ChimeraCommands.java
index 41c7abb..3c47ed1 100644 (file)
@@ -22,16 +22,21 @@ package jalview.ext.rbvi.chimera;
 
 import jalview.api.FeatureRenderer;
 import jalview.api.SequenceRenderer;
-import jalview.api.structures.JalviewStructureDisplayI;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.SequenceI;
 import jalview.structure.StructureMapping;
 import jalview.structure.StructureMappingcommandSet;
 import jalview.structure.StructureSelectionManager;
-import jalview.util.Format;
+import jalview.util.Comparison;
 
 import java.awt.Color;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
  * Routines for generating Chimera commands for Jalview/Chimera binding
@@ -54,109 +59,257 @@ public class ChimeraCommands
           SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
           AlignmentI alignment)
   {
+    String defAttrPath = null;
+    FileOutputStream fos = null;
+    try
+    {
+      File outFile = File.createTempFile("jalviewdefattr", ".xml");
+      outFile.deleteOnExit();
+      defAttrPath = outFile.getPath();
+      fos = new FileOutputStream(outFile);
+      fos.write("attribute: jalviewclr\n".getBytes());
+    } catch (IOException e1)
+    {
+      e1.printStackTrace();
+    }
+    List<StructureMappingcommandSet> cset = new ArrayList<StructureMappingcommandSet>();
 
-    ArrayList<StructureMappingcommandSet> cset = new ArrayList<StructureMappingcommandSet>();
-
+    /*
+     * Map of { colour, positionSpecs}
+     */
+    Map<String, StringBuilder> colranges = new LinkedHashMap<String, StringBuilder>();
+    StringBuilder setAttributes = new StringBuilder(256);
+    String lastColour = "none";
+    Color lastCol = null;
     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
     {
-      float cols[] = new float[4];
+      boolean startModel = true;
       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
-      StringBuffer command = new StringBuffer();
-      StructureMappingcommandSet smc;
-      ArrayList<String> str = new ArrayList<String>();
 
       if (mapping == null || mapping.length < 1)
+      {
         continue;
+      }
 
-      int startPos = -1, lastPos = -1, startModel = -1, lastModel = -1;
-      String startChain = "", lastChain = "";
-      Color lastCol = null;
+      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++)
         {
-          if (mapping[m].getSequence() == sequence[pdbfnum][s]
-                  && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1)
+          final SequenceI seq = sequence[pdbfnum][s];
+          if (mapping[m].getSequence() == seq
+                  && (sp = alignment.findIndex(seq)) > -1)
           {
             SequenceI asp = alignment.getSequenceAt(sp);
             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;
               }
               int pos = mapping[m].getPDBResNum(asp.findPosition(r));
 
               if (pos < 1 || pos == lastPos)
+              {
                 continue;
+              }
 
-              Color col = sr.getResidueBoxColour(sequence[pdbfnum][s], r);
-
-              if (fr != null)
-                col = fr.findFeatureColour(col, sequence[pdbfnum][s], r);
-              if (lastCol != col || lastPos + 1 != pos
-                      || pdbfnum != lastModel
-                      || !mapping[m].getChain().equals(lastChain))
+              Color col = getResidueColour(seq, r, sr, fr);
+              /*
+               * 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 = !col.equals(lastCol);
+              final boolean nonContig = lastPos + 1 != pos;
+              final boolean newChain = !mapping[m].getChain().equals(lastChain);
+              if (newColour || nonContig || startModel || newChain)
               {
-                if (lastCol != null)
+                if (/* lastCol != null */startPos != -1)
                 {
-
-                  lastCol.getRGBComponents(cols);
-                  String newSelcom = "color " + cols[0] + "," + cols[1]
-                          + "," + cols[2] + " #" + startModel + ":"
-                          + startPos + "-" + lastPos + "." + lastChain;
-                  if (str.size() > 0
-                          && (str.get(str.size() - 1).length() + newSelcom
-                                  .length()) < 4096)
-                  {
-                    str.set(str.size() - 1, str.get(str.size() - 1) + ";"
-                            + newSelcom);
-                  }
-                  else
-                  {
-                    str.add(newSelcom);
-                  }
+                  addColourRange(colranges, lastCol, pdbfnum, startPos,
+                          lastPos, lastChain, startModel);
+                  startModel = false;
                 }
-                lastCol = null;
+                // lastCol = null;
                 startPos = pos;
-                startModel = pdbfnum;
-                startChain = mapping[m].getChain();
               }
               lastCol = col;
               lastPos = pos;
-              lastModel = pdbfnum;
+              // lastModel = pdbfnum;
               lastChain = mapping[m].getChain();
             }
             // final colour range
             if (lastCol != null)
             {
-
-              lastCol.getRGBComponents(cols);
-              String newSelcom = "color " + cols[0] + "," + cols[1] + ","
-                      + cols[2] + " #" + startModel + ":" + startPos + "-"
-                      + lastPos + "." + lastChain;
-              if (str.size() > 0
-                      && (str.get(str.size() - 1).length() + newSelcom
-                              .length()) < 4096)
-              {
-                str.set(str.size() - 1, str.get(str.size() - 1) + ";"
-                        + newSelcom);
-              }
-              else
-              {
-                str.add(newSelcom);
-              }
+              addColourRange(colranges, lastCol, pdbfnum, startPos,
+                      lastPos, lastChain, false);
             }
             break;
           }
         }
       }
-      // Finally, add the command set ready to be returned.
-      cset.add(new StructureMappingcommandSet(ChimeraCommands.class,
-              files[pdbfnum], str.toArray(new String[str.size()])));
     }
+      try
+      {
+      lastColour = buildColourCommands(cset, colranges,
+                fos, setAttributes);
+      } catch (IOException e)
+      {
+        e.printStackTrace();
+      }
+
+    try
+    {
+      fos.close();
+    } catch (IOException e)
+    {
+      e.printStackTrace();
+    }
+
+    /*
+     * Send a rangeColor command, preceded by either defattr or setattr,
+     * whichever we end up preferring!
+     * 
+     * rangecolor requires a minimum of two attribute values to operate on
+     */
+    StringBuilder rangeColor = new StringBuilder(256);
+    rangeColor.append("rangecolor jalviewclr");
+    int colourId = 0;
+    for (String colour : colranges.keySet())
+    {
+      colourId++;
+      rangeColor.append(" " + colourId + " " + colour);
+    }
+    String rangeColorCommand = rangeColor.toString();
+    if (rangeColorCommand.split(" ").length < 5)
+    {
+      rangeColorCommand += " max " + lastColour;
+    }
+    final String defAttrCommand = "defattr " + defAttrPath
+            + " raiseTool false";
+    final String setAttrCommand = setAttributes.toString();
+    final String attrCommand = false ? defAttrCommand : setAttrCommand;
+    cset.add(new StructureMappingcommandSet(ChimeraCommands.class, null,
+            new String[]
+            { attrCommand /* , rangeColorCommand */}));
+
     return cset.toArray(new StructureMappingcommandSet[cset.size()]);
   }
 
+  /**
+   * Get the residue colour at the given sequence position - as determined by
+   * the sequence group colour (if any), else the colour scheme, possibly
+   * overridden by a feature colour.
+   * 
+   * @param seq
+   * @param position
+   * @param sr
+   * @param fr
+   * @return
+   */
+  protected static Color getResidueColour(final SequenceI seq,
+          int position, SequenceRenderer sr, FeatureRenderer fr)
+  {
+    Color col = sr.getResidueBoxColour(seq, position);
+
+    if (fr != null)
+    {
+      col = fr.findFeatureColour(col, seq, position);
+    }
+    return col;
+  }
+
+  /**
+   * Helper method to build the colour commands for one PDBfile.
+   * 
+   * @param cset
+   *          the list of commands to be added to
+   * @param colranges
+   *          the map of colours to residue positions already determined
+   * @param fos
+   *          file to write 'defattr' commands to
+   * @param setAttributes
+   * @throws IOException
+   */
+  protected static String buildColourCommands(
+          List<StructureMappingcommandSet> cset,
+          Map<String, StringBuilder> colranges,
+          FileOutputStream fos, StringBuilder setAttributes)
+          throws IOException
+  {
+    int colourId = 0;
+    String lastColour = null;
+    for (String colour : colranges.keySet())
+    {
+      lastColour = colour;
+      colourId++;
+      /*
+       * Using color command directly is slow for larger structures.
+       * setAttributes.append("color #" + colour + " " + colranges.get(colour)+
+       * ";");
+       */
+      setAttributes.append("color " + colour + " " + colranges.get(colour)
+              + ";");
+      final String atomSpec = new String(colranges.get(colour));
+      // setAttributes.append("setattr r jalviewclr " + colourId + " "
+      // + atomSpec + ";");
+      fos.write(("\t" + atomSpec + "\t" + colourId + "\n").getBytes());
+    }
+    return lastColour;
+  }
+
+  /**
+   * Helper method to record a range of positions of the same colour.
+   * 
+   * @param colranges
+   * @param colour
+   * @param model
+   * @param startPos
+   * @param endPos
+   * @param chain
+   * @param changeModel
+   */
+  private static void addColourRange(Map<String, StringBuilder> colranges,
+          Color colour, int model, int startPos, int endPos, String chain,
+          boolean startModel)
+  {
+    String colstring = "#" + ((colour.getRed() < 16) ? "0" : "")
+            + Integer.toHexString(colour.getRed())
+            + ((colour.getGreen()< 16) ? "0":"")+Integer.toHexString(colour.getGreen())
+            + ((colour.getBlue()< 16) ? "0":"")+Integer.toHexString(colour.getBlue());
+    StringBuilder currange = colranges.get(colstring);
+    if (currange == null)
+    {
+      colranges.put(colstring, currange = new StringBuilder(256));
+    }
+    /*
+     * Format as (e.g.) #0:1-3.A,5.A,7-10.A,...#1:1-4.B,..etc
+     */
+    // if (currange.length() > 0)
+    // {
+    // currange.append("|");
+    // }
+    // currange.append("#" + model + ":" + ((startPos==endPos) ? startPos :
+    // startPos + "-"
+    // + endPos) + "." + chain);
+    if (currange.length() == 0)
+    {
+      currange.append("#" + model + ":");
+    }
+    else if (startModel)
+    {
+      currange.append(",#" + model + ":");
+    }
+    else
+    {
+      currange.append(",");
+    }
+    final String rangeSpec = (startPos == endPos) ? Integer
+            .toString(startPos) : (startPos + "-" + endPos);
+    currange.append(rangeSpec + "." + chain);
+  }
+
 }