3 import jalview.api.AlignViewportI;
4 import jalview.api.AlignmentViewPanel;
5 import jalview.api.FeatureRenderer;
6 import jalview.api.SequenceRenderer;
7 import jalview.datamodel.AlignmentI;
8 import jalview.datamodel.HiddenColumns;
9 import jalview.datamodel.SequenceI;
10 import jalview.ext.rbvi.chimera.AtomSpecModel;
11 import jalview.renderer.seqfeatures.FeatureColourFinder;
12 import jalview.structure.StructureMapping;
13 import jalview.structures.models.AAStructureBindingModel;
15 import java.awt.Color;
16 import java.util.Collections;
17 import java.util.Iterator;
18 import java.util.LinkedHashMap;
19 import java.util.List;
23 * A class with common methods for building commands for Jmol, Chimera, or other
27 public abstract class StructureCommands
31 * Helper method to add one contiguous range to the AtomSpec model for the given
32 * value (creating the model if necessary). As used by Jalview, {@code value} is
34 * <li>a colour, when building a 'colour structure by sequence' command</li>
35 * <li>a feature value, when building a 'set Chimera attributes from features'
46 public static void addAtomSpecRange(Map<Object, AtomSpecModel> map,
48 int model, int startPos, int endPos, String chain)
51 * Get/initialize map of data for the colour
53 AtomSpecModel atomSpec = map.get(value);
56 atomSpec = new AtomSpecModel();
57 map.put(value, atomSpec);
60 atomSpec.addRange(model, startPos, endPos, chain);
64 * Build a data structure which records contiguous subsequences by model and
65 * chain. From this we can easily generate the Chimera or Jmol specific
66 * selection expression.
72 * list of start/end ranges
75 * Ordering is by order of addition (for colours and positions), natural
76 * ordering (for models and chains)
81 public static Map<Object, AtomSpecModel> buildColoursMap(
82 AAStructureBindingModel binding, AlignmentViewPanel viewPanel)
84 FeatureRenderer fr = viewPanel.getFeatureRenderer();
85 FeatureColourFinder finder = new FeatureColourFinder(fr);
86 AlignViewportI viewport = viewPanel.getAlignViewport();
87 HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
88 AlignmentI al = viewport.getAlignment();
89 SequenceRenderer sr = binding.getSequenceRenderer(viewPanel);
90 String[] files = binding.getStructureFiles();
91 SequenceI[][] sequence = binding.getSequence();
93 Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<>();
94 Color lastColour = null;
96 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
98 StructureMapping[] mapping = binding.getSsm()
99 .getMapping(files[pdbfnum]);
101 if (mapping == null || mapping.length < 1)
106 int startPos = -1, lastPos = -1;
107 String lastChain = "";
108 for (int s = 0; s < sequence[pdbfnum].length; s++)
110 for (int sp, m = 0; m < mapping.length; m++)
112 final SequenceI seq = sequence[pdbfnum][s];
113 if (mapping[m].getSequence() == seq
114 && (sp = al.findIndex(seq)) > -1)
116 SequenceI asp = al.getSequenceAt(sp);
117 for (int r = 0; r < asp.getLength(); r++)
119 // no mapping to gaps in sequence
120 if (Comparison.isGap(asp.getCharAt(r)))
124 int pos = mapping[m].getPDBResNum(asp.findPosition(r));
126 if (pos < 1 || pos == lastPos)
131 Color colour = sr.getResidueColour(seq, r, finder);
134 * hidden regions are shown gray or, optionally, ignored
136 if (!cs.isVisible(r))
138 if (binding.isHideHiddenRegions())
148 final String chain = mapping[m].getChain();
151 * Just keep incrementing the end position for this colour range
152 * _unless_ colour, PDB model or chain has changed, or there is a
153 * gap in the mapped residue sequence
155 final boolean newColour = !colour.equals(lastColour);
156 final boolean nonContig = lastPos + 1 != pos;
157 final boolean newChain = !chain.equals(lastChain);
158 if (newColour || nonContig || newChain)
162 StructureCommands.addAtomSpecRange(colourMap, lastColour,
172 // final colour range
173 if (lastColour != null)
175 StructureCommands.addAtomSpecRange(colourMap, lastColour,
177 startPos, lastPos, lastChain);
187 * A helper method that takes a list of [start-end] ranges, format them as
188 * s1-e1,s2-e2 etc and appends to the string buffer. Ranges are sorted, and
189 * coalesced if they overlap. The chain token, if not null, is appended to each
190 * resulting subrange.
195 * @param firstPositionForModel
197 protected static void appendResidueRange(StringBuilder sb, List<int[]> rangeList,
198 String chainToken, boolean firstPositionForModel)
201 * sort ranges into ascending start position order
203 Collections.sort(rangeList, IntRangeComparator.ASCENDING);
205 int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0];
206 int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1];
208 Iterator<int[]> iterator = rangeList.iterator();
209 while (iterator.hasNext())
211 int[] range = iterator.next();
212 if (range[0] <= end + 1)
215 * range overlaps or is contiguous with the last one
216 * - so just extend the end position, and carry on
217 * (unless this is the last in the list)
219 end = Math.max(end, range[1]);
224 * we have a break so append the last range
226 appendRange(sb, start, end, chainToken, firstPositionForModel);
227 firstPositionForModel = false;
234 * and append the last range
236 if (!rangeList.isEmpty())
238 appendRange(sb, start, end, chainToken, firstPositionForModel);
243 * A helper method that appends one start-end range, and an (optional) chain
244 * token to an atomspec (a null token is not appended)
250 * @param firstPositionForModel
252 protected static void appendRange(StringBuilder sb, int start, int end,
253 String chainToken, boolean firstPositionForModel)
255 if (!firstPositionForModel)
265 sb.append(start).append("-").append(end);
267 if (chainToken != null)
269 sb.append(chainToken);