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; } /** *
   * Build a data structure which records contiguous subsequences for each colour. 
   * From this we can easily generate the viewer 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)
   * 
* * @param ssm * @param files * @param sequence * @param sr * @param viewPanel * @return */ protected Map 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 colourMap = new LinkedHashMap<>(); Color lastColour = null; 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) { 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 * * * @param map * @param value * @param model * @param startPos * @param endPos * @param chain */ public static final void addAtomSpecRange(Map 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); } /** * Returns a colour formatted suitable for use in viewer command syntax * * @param colour * @return */ protected abstract String getColourString(Color colour); /** * 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 */ public List buildColourCommands( Map 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 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; } /** * 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 colours) { StringBuilder cmd = new StringBuilder(12 * colours.size()); for (Entry 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); }