921c9cde88b8dafe1982b45b07a4e416509cce81
[jalview.git] / src / jalview / structure / StructureCommandsBase.java
1 package jalview.structure;
2
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.renderer.seqfeatures.FeatureColourFinder;
11 import jalview.util.Comparison;
12
13 import java.awt.Color;
14 import java.util.ArrayList;
15 import java.util.LinkedHashMap;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Map.Entry;
19
20 /**
21  * A base class holding methods useful to all classes that implement commands
22  * for structure viewers
23  * 
24  * @author gmcarstairs
25  *
26  */
27 public abstract class StructureCommandsBase implements StructureCommandsI
28 {
29   private static final String CMD_SEPARATOR = ";";
30
31   /**
32    * Returns something that separates concatenated commands
33    * 
34    * @return
35    */
36   protected static String getCommandSeparator()
37   {
38     return CMD_SEPARATOR;
39   }
40
41   @Override
42   public String[] setAttributesForFeatures(StructureSelectionManager ssm,
43           String[] files, SequenceI[][] sequence, AlignmentViewPanel avp)
44   {
45     // default does nothing, override where this is implemented
46     return null;
47   }
48
49   /**
50    * Returns the lowest model number used by the structure viewer
51    * 
52    * @return
53    */
54   @Override
55   public int getModelStartNo()
56   {
57     return 0;
58   }
59
60   /**
61    * <pre>
62    * Build a data structure which records contiguous subsequences for each colour. 
63    * From this we can easily generate the viewer command for colour by sequence.
64    * Color
65    *     Model number
66    *         Chain
67    *             list of start/end ranges
68    * Ordering is by order of addition (for colours and positions), natural ordering (for models and chains)
69    * </pre>
70    * 
71    * @param ssm
72    * @param files
73    * @param sequence
74    * @param sr
75    * @param viewPanel
76    * @return
77    */
78   protected Map<Object, AtomSpecModel> buildColoursMap(
79           StructureSelectionManager ssm, String[] files,
80           SequenceI[][] sequence, SequenceRenderer sr, AlignmentViewPanel viewPanel)
81   {
82     FeatureRenderer fr = viewPanel.getFeatureRenderer();
83     FeatureColourFinder finder = new FeatureColourFinder(fr);
84     AlignViewportI viewport = viewPanel.getAlignViewport();
85     HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
86     AlignmentI al = viewport.getAlignment();
87     Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<>();
88     Color lastColour = null;
89   
90     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
91     {
92       final int modelNumber = pdbfnum + getModelStartNo();
93       StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
94   
95       if (mapping == null || mapping.length < 1)
96       {
97         continue;
98       }
99   
100       int startPos = -1, lastPos = -1;
101       String lastChain = "";
102       for (int s = 0; s < sequence[pdbfnum].length; s++)
103       {
104         for (int sp, m = 0; m < mapping.length; m++)
105         {
106           final SequenceI seq = sequence[pdbfnum][s];
107           if (mapping[m].getSequence() == seq
108                   && (sp = al.findIndex(seq)) > -1)
109           {
110             SequenceI asp = al.getSequenceAt(sp);
111             for (int r = 0; r < asp.getLength(); r++)
112             {
113               // no mapping to gaps in sequence
114               if (Comparison.isGap(asp.getCharAt(r)))
115               {
116                 continue;
117               }
118               int pos = mapping[m].getPDBResNum(asp.findPosition(r));
119   
120               if (pos < 1 || pos == lastPos)
121               {
122                 continue;
123               }
124   
125               Color colour = sr.getResidueColour(seq, r, finder);
126   
127               /*
128                * darker colour for hidden regions
129                */
130               if (!cs.isVisible(r))
131               {
132                 colour = Color.GRAY;
133               }
134   
135               final String chain = mapping[m].getChain();
136   
137               /*
138                * Just keep incrementing the end position for this colour range
139                * _unless_ colour, PDB model or chain has changed, or there is a
140                * gap in the mapped residue sequence
141                */
142               final boolean newColour = !colour.equals(lastColour);
143               final boolean nonContig = lastPos + 1 != pos;
144               final boolean newChain = !chain.equals(lastChain);
145               if (newColour || nonContig || newChain)
146               {
147                 if (startPos != -1)
148                 {
149                   addAtomSpecRange(colourMap, lastColour, modelNumber,
150                           startPos, lastPos, lastChain);
151                 }
152                 startPos = pos;
153               }
154               lastColour = colour;
155               lastPos = pos;
156               lastChain = chain;
157             }
158             // final colour range
159             if (lastColour != null)
160             {
161               addAtomSpecRange(colourMap, lastColour, modelNumber, startPos,
162                       lastPos, lastChain);
163             }
164             // break;
165           }
166         }
167       }
168     }
169     return colourMap;
170   }
171
172   /**
173    * Helper method to add one contiguous range to the AtomSpec model for the given
174    * value (creating the model if necessary). As used by Jalview, {@code value} is
175    * <ul>
176    * <li>a colour, when building a 'colour structure by sequence' command</li>
177    * <li>a feature value, when building a 'set Chimera attributes from features'
178    * command</li>
179    * </ul>
180    * 
181    * @param map
182    * @param value
183    * @param model
184    * @param startPos
185    * @param endPos
186    * @param chain
187    */
188   public static final void addAtomSpecRange(Map<Object, AtomSpecModel> map,
189           Object value,
190           int model, int startPos, int endPos, String chain)
191   {
192     /*
193      * Get/initialize map of data for the colour
194      */
195     AtomSpecModel atomSpec = map.get(value);
196     if (atomSpec == null)
197     {
198       atomSpec = new AtomSpecModel();
199       map.put(value, atomSpec);
200     }
201   
202     atomSpec.addRange(model, startPos, endPos, chain);
203   }
204
205   /**
206    * Returns a colour formatted suitable for use in viewer command syntax
207    * 
208    * @param colour
209    * @return
210    */
211   protected abstract String getColourString(Color colour);
212
213   /**
214    * Traverse the map of colours/models/chains/positions to construct a list of
215    * 'color' commands (one per distinct colour used). The format of each command
216    * is specific to the structure viewer.
217    * 
218    * @param colourMap
219    * @return
220    */
221   public List<String> buildColourCommands(
222           Map<Object, AtomSpecModel> colourMap)
223   {
224     /*
225      * This version concatenates all commands into a single String (semi-colon
226      * delimited). If length limit issues arise, refactor to return one color
227      * command per colour.
228      */
229     List<String> commands = new ArrayList<>();
230     StringBuilder sb = new StringBuilder(256);
231     boolean firstColour = true;
232     for (Object key : colourMap.keySet())
233     {
234       Color colour = (Color) key;
235       if (!firstColour)
236       {
237         sb.append(getCommandSeparator()).append(" ");
238       }
239       firstColour = false;
240       final AtomSpecModel colourData = colourMap.get(colour);
241       sb.append(getColourCommand(colourData, colour));
242     }
243     commands.add(sb.toString());
244     return commands;
245   }
246
247   /**
248    * Returns a command to colour the atoms represented by {@code atomSpecModel}
249    * with the colour specified by {@code colourCode}.
250    * 
251    * @param atomSpecModel
252    * @param colour
253    * @return
254    */
255   protected String getColourCommand(AtomSpecModel atomSpecModel, Color colour)
256   {
257     String atomSpec = getAtomSpec(atomSpecModel, false);
258     return getColourCommand(atomSpec, colour);
259   }
260
261   /**
262    * Returns a command to colour the atoms described (in viewer command syntax)
263    * by {@code atomSpec} with the colour specified by {@code colourCode}
264    * 
265    * @param atomSpec
266    * @param colour
267    * @return
268    */
269   protected abstract String getColourCommand(String atomSpec, Color colour);
270
271   @Override
272   public String colourByResidues(Map<String, Color> colours)
273   {
274     StringBuilder cmd = new StringBuilder(12 * colours.size());
275   
276     for (Entry<String, Color> entry : colours.entrySet())
277     {
278       String residue = entry.getKey();
279       String atomSpec = getResidueSpec(residue);
280       cmd.append(getColourCommand(atomSpec, entry.getValue()));
281       cmd.append(getCommandSeparator());
282     }
283     return cmd.toString();
284   }
285
286   /**
287    * Helper method to append one start-end range to an atomspec string
288    * 
289    * @param sb
290    * @param start
291    * @param end
292    * @param chain
293    * @param firstPositionForModel
294    */
295   protected void appendRange(StringBuilder sb, int start, int end,
296           String chain, boolean firstPositionForModel, boolean isChimeraX)
297   {
298     if (!firstPositionForModel)
299     {
300       sb.append(",");
301     }
302     if (end == start)
303     {
304       sb.append(start);
305     }
306     else
307     {
308       sb.append(start).append("-").append(end);
309     }
310
311     if (!isChimeraX)
312     {
313       sb.append(".");
314       if (!" ".equals(chain))
315       {
316         sb.append(chain);
317       }
318     }
319   }
320
321   /**
322    * Returns the atom specifier meaning all occurrences of the given residue
323    * 
324    * @param residue
325    * @return
326    */
327   protected abstract String getResidueSpec(String residue);
328 }