package jalview.util; 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.ext.rbvi.chimera.AtomSpecModel; import jalview.renderer.seqfeatures.FeatureColourFinder; import jalview.structure.StructureMapping; import jalview.structures.models.AAStructureBindingModel; import java.awt.Color; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * A class with common methods for building commands for Jmol, Chimera, or other * * @author gmcarstairs */ public abstract class StructureCommands { /** * 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 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); } /** * Build a data structure which records contiguous subsequences by colour, model * and chain. From this we can easily generate the Chimera or Jmol specific * selection expression. * *
   * 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 viewPanel * @return */ public static Map buildColoursMap( AAStructureBindingModel binding, AlignmentViewPanel viewPanel) { FeatureRenderer fr = viewPanel.getFeatureRenderer(); FeatureColourFinder finder = new FeatureColourFinder(fr); AlignViewportI viewport = viewPanel.getAlignViewport(); HiddenColumns cs = viewport.getAlignment().getHiddenColumns(); AlignmentI al = viewport.getAlignment(); SequenceRenderer sr = binding.getSequenceRenderer(viewPanel); String[] files = binding.getStructureFiles(); SequenceI[][] sequence = binding.getSequence(); Map colourMap = new LinkedHashMap<>(); Color lastColour = null; for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) { String fileName = files[pdbfnum]; final int modelNumber = binding.getModelForPdbFile(fileName, pdbfnum); StructureMapping[] mapping = binding.getSsm() .getMapping(fileName); 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); /* * hidden regions are shown gray or, optionally, ignored */ if (!cs.isVisible(r)) { if (binding.isHideHiddenRegions()) { continue; } else { 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) { StructureCommands.addAtomSpecRange(colourMap, lastColour, modelNumber, startPos, lastPos, lastChain); } startPos = pos; } lastColour = colour; lastPos = pos; lastChain = chain; } // final colour range if (lastColour != null) { StructureCommands.addAtomSpecRange(colourMap, lastColour, modelNumber, startPos, lastPos, lastChain); } } } } } return colourMap; } /** * A helper method that takes a list of [start-end] ranges, format them as * s1-e1,s2-e2 etc and appends to the string buffer. Ranges are sorted, and * coalesced if they overlap. The chain token, if not null, is appended to each * resulting subrange. * * @param sb * @param rangeList * @param chainToken * @param firstPositionForModel */ protected static void appendResidueRange(StringBuilder sb, List rangeList, String chainToken, boolean firstPositionForModel) { /* * 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 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, chainToken, firstPositionForModel); firstPositionForModel = false; start = range[0]; end = range[1]; } } /* * and append the last range */ if (!rangeList.isEmpty()) { appendRange(sb, start, end, chainToken, firstPositionForModel); } } /** * A helper method that appends one start-end range, and an (optional) chain * token to an atomspec (a null token is not appended) * * @param sb * @param start * @param end * @param chainToken * @param firstPositionForModel */ protected static void appendRange(StringBuilder sb, int start, int end, String chainToken, boolean firstPositionForModel) { if (!firstPositionForModel) { sb.append(","); } if (end == start) { sb.append(start); } else { sb.append(start).append("-").append(end); } if (chainToken != null) { sb.append(chainToken); } } }