JAL-3253 JAL-3442 just comment tidying
[jalview.git] / src / jalview / renderer / seqfeatures / FeatureRenderer.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.renderer.seqfeatures;
22
23 import jalview.api.AlignViewportI;
24 import jalview.api.FeatureColourI;
25 import jalview.datamodel.ContiguousI;
26 import jalview.datamodel.SequenceFeature;
27 import jalview.datamodel.SequenceI;
28 import jalview.util.Comparison;
29 import jalview.util.Platform;
30 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
31
32 import java.awt.AlphaComposite;
33 import java.awt.Color;
34 import java.awt.FontMetrics;
35 import java.awt.Graphics;
36 import java.awt.Graphics2D;
37 import java.util.ArrayList;
38 import java.util.List;
39
40 public class FeatureRenderer extends FeatureRendererModel
41 {
42   private static final AlphaComposite NO_TRANSPARENCY = AlphaComposite
43           .getInstance(AlphaComposite.SRC_OVER, 1.0f);
44
45   /*
46    * persistent list used by JalviewJS; not threadsafe for Java
47    */
48   private List<SequenceFeature> overlaps = (Platform.isJS()
49           ? new ArrayList<>()
50           : null);
51
52   /**
53    * Constructor given a viewport
54    * 
55    * @param viewport
56    */
57   public FeatureRenderer(AlignViewportI viewport)
58   {
59     this.av = viewport;
60   }
61
62   /**
63    * Renders the sequence using the given feature colour between the given start
64    * and end columns. Returns true if at least one column is drawn, else false
65    * (the feature range does not overlap the start and end positions).
66    * 
67    * @param g
68    * @param seq
69    * @param featureStart
70    * @param featureEnd
71    * @param featureColour
72    * @param start
73    * @param end
74    * @param y1
75    * @param colourOnly
76    * @return
77    */
78   boolean renderFeature(Graphics g, SequenceI seq, int featureStart,
79           int featureEnd, Color featureColour, int start, int end, int y1,
80           boolean colourOnly)
81   {
82     int charHeight = av.getCharHeight();
83     int charWidth = av.getCharWidth();
84     boolean validCharWidth = av.isValidCharWidth();
85
86     if (featureStart > end || featureEnd < start)
87     {
88       return false;
89     }
90
91     if (featureStart < start)
92     {
93       featureStart = start;
94     }
95     if (featureEnd >= end)
96     {
97       featureEnd = end;
98     }
99     int pady = (y1 + charHeight) - charHeight / 5;
100
101     FontMetrics fm = g.getFontMetrics();
102     char s = '\0';
103     for (int i = featureStart; i <= featureEnd; i++)
104     {
105       if (!colourOnly && Comparison.isGap(s = seq.getCharAt(i)))
106       {
107         continue;
108       }
109
110       g.setColor(featureColour);
111
112       g.fillRect((i - start) * charWidth, y1, charWidth, charHeight);
113
114       if (colourOnly)
115       {
116         return true;
117       }
118
119       if (!validCharWidth)
120       {
121         continue;
122       }
123
124       g.setColor(Color.white);
125       int charOffset = (charWidth - fm.charWidth(s)) / 2;
126       g.drawString(String.valueOf(s),
127               charOffset + (charWidth * (i - start)), pady);
128     }
129     return true;
130   }
131
132   /**
133    * 
134    * BH - this method is never called?
135    * 
136    * Renders the sequence using the given SCORE feature colour between the given
137    * start and end columns. Returns true if at least one column is drawn, else
138    * false (the feature range does not overlap the start and end positions).
139    * 
140    * @param g
141    * @param seq
142    * @param fstart
143    * @param fend
144    * @param featureColour
145    * @param start
146    * @param end
147    * @param y1
148    * @param bs
149    * @param colourOnly
150    * @return
151    */
152   boolean renderScoreFeature(Graphics g, SequenceI seq, int fstart,
153           int fend, Color featureColour, int start, int end, int y1,
154           byte[] bs, boolean colourOnly)
155   {
156     if (fstart > end || fend < start)
157     {
158       return false;
159     }
160
161     if (fstart < start)
162     { // fix for if the feature we have starts before the sequence start,
163       fstart = start; // but the feature end is still valid!!
164     }
165
166     if (fend >= end)
167     {
168       fend = end;
169     }
170     int charHeight = av.getCharHeight();
171     int pady = (y1 + charHeight) - charHeight / 5;
172     int ystrt = 0, yend = charHeight;
173     if (bs[0] != 0)
174     {
175       // signed - zero is always middle of residue line.
176       if (bs[1] < 128)
177       {
178         yend = charHeight * (128 - bs[1]) / 512;
179         ystrt = charHeight - yend / 2;
180       }
181       else
182       {
183         ystrt = charHeight / 2;
184         yend = charHeight * (bs[1] - 128) / 512;
185       }
186     }
187     else
188     {
189       yend = charHeight * bs[1] / 255;
190       ystrt = charHeight - yend;
191
192     }
193
194     FontMetrics fm = g.getFontMetrics();
195     int charWidth = av.getCharWidth();
196
197     for (int i = fstart; i <= fend; i++)
198     {
199       char s = seq.getCharAt(i);
200
201       if (Comparison.isGap(s))
202       {
203         continue;
204       }
205
206       g.setColor(featureColour);
207       int x = (i - start) * charWidth;
208       g.drawRect(x, y1, charWidth, charHeight);
209       g.fillRect(x, y1 + ystrt, charWidth, yend);
210
211       if (colourOnly || !av.isValidCharWidth())
212       {
213         continue;
214       }
215
216       g.setColor(Color.black);
217       int charOffset = (charWidth - fm.charWidth(s)) / 2;
218       g.drawString(String.valueOf(s),
219               charOffset + (charWidth * (i - start)), pady);
220     }
221     return true;
222   }
223
224   /**
225    * {@inheritDoc}
226    */
227   @Override
228   public Color findFeatureColour(SequenceI seq, int column, Graphics g)
229   {
230     // column is 'base 1' but getCharAt is an array index (ie from 0)
231     if (Comparison.isGap(seq.getCharAt(column - 1)))
232     {
233       /*
234        * returning null allows the colour scheme to provide gap colour
235        * - normally white, but can be customised
236        */
237       return null;
238     }
239
240     Color renderedColour = null;
241     if (transparency == 1.0f)
242     {
243       /*
244        * simple case - just find the topmost rendered visible feature colour
245        */
246       renderedColour = findFeatureColour(seq, column);
247     }
248     else
249     {
250       /*
251        * transparency case - draw all visible features in render order to
252        * build up a composite colour on the graphics context
253        */
254       renderedColour = drawSequence(g, seq, column, column, 0, true);
255     }
256     return renderedColour;
257   }
258
259   /**
260    * Draws the sequence features on the graphics context, or just determines the
261    * colour that would be drawn (if flag colourOnly is true). Returns the last
262    * colour drawn (which may not be the effective colour if transparency
263    * applies), or null if no feature is drawn in the range given.
264    * 
265    * @param g
266    *          the graphics context to draw on (null if no transparency applies)
267    * @param seq
268    * @param start
269    *          start column
270    * @param end
271    *          end column
272    * @param y1
273    *          vertical offset at which to draw on the graphics
274    * @param colourOnly
275    *          if true, only do enough to determine the colour for the position,
276    *          do not draw the character
277    * @return
278    */
279   public synchronized Color drawSequence(final Graphics g,
280           final SequenceI seq, int start, int end, int y1,
281           boolean colourOnly)
282   {
283     /*
284      * if columns are all gapped, or sequence has no features, nothing to do
285      */
286     ContiguousI visiblePositions;
287     if (!seq.getFeatures().hasFeatures() || (visiblePositions = seq
288             .findPositions(start + 1, end + 1)) == null)
289     {
290       return null;
291     }
292
293     int vp0 = visiblePositions.getBegin();
294     int vp1 = visiblePositions.getEnd();
295
296     updateFeatures();
297
298     if (transparency != 1f)
299     {
300       Graphics2D g2 = (Graphics2D) g;
301       g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
302               transparency));
303     }
304
305     Color drawnColour = null;
306
307     /*
308      * iterate over features in ordering of their rendering (last is on top)
309      */
310     for (int renderIndex = 0, n = renderOrder.length; renderIndex < n; renderIndex++)
311     {
312       String type = renderOrder[renderIndex];
313       if (!seq.hasFeatures(type) || !showFeatureOfType(type))
314       {
315         continue;
316       }
317
318       FeatureColourI fc = getFeatureStyle(type);
319       List<SequenceFeature> overlaps = seq.getFeatures().findFeatures(vp0,
320               vp1, type);
321
322       // colourOnly (i.e. Overview) can only be here if translucent, so
323       // there is no need to check for filtering
324       if (!colourOnly && fc.isSimpleColour())
325       {
326         filterFeaturesForDisplay(overlaps);
327       }
328
329       for (int i = 0, j = overlaps.size(); i < j; i++)
330       {
331         SequenceFeature sf = overlaps.get(i);
332         Color featureColour = getColor(sf, fc);
333         if (featureColour == null)
334         {
335           /*
336            * feature excluded by visibility settings, filters, or colour threshold
337            */
338           continue;
339         }
340
341         /*
342          * if feature starts/ends outside the visible range,
343          * restrict to visible positions (or if a contact feature,
344          * to a single position)
345          */
346         int sf0 = sf.getBegin();
347         int sf1 = sf.getEnd();
348         int visibleStart = sf0;
349         if (visibleStart < vp0)
350         {
351           visibleStart = sf.isContactFeature() ? sf1 : vp0;
352         }
353         int visibleEnd = sf1;
354         if (visibleEnd > vp1)
355         {
356           visibleEnd = sf.isContactFeature() ? sf0 : vp1;
357         }
358
359         int featureStartCol = seq.findIndex(visibleStart);
360         int featureEndCol = (sf.begin == sf.end ? featureStartCol
361                 : seq.findIndex(visibleEnd));
362
363         // Color featureColour = getColour(sequenceFeature);
364
365         boolean isContactFeature = sf.isContactFeature();
366
367         if (isContactFeature)
368         {
369           boolean drawn = renderFeature(g, seq, featureStartCol - 1,
370                   featureStartCol - 1, featureColour, start, end, y1,
371                   colourOnly);
372           drawn |= renderFeature(g, seq, featureEndCol - 1,
373                   featureEndCol - 1, featureColour, start, end, y1,
374                   colourOnly);
375           if (drawn)
376           {
377             drawnColour = featureColour;
378           }
379         }
380         else
381         {
382           /*
383            * showing feature score by height of colour
384            * is not implemented as a selectable option 
385            *
386           if (av.isShowSequenceFeaturesHeight()
387                   && !Float.isNaN(sequenceFeature.score))
388           {
389             boolean drawn = renderScoreFeature(g, seq,
390                     seq.findIndex(sequenceFeature.begin) - 1,
391                     seq.findIndex(sequenceFeature.end) - 1, featureColour,
392                     start, end, y1, normaliseScore(sequenceFeature),
393                     colourOnly);
394             if (drawn)
395             {
396               drawnColour = featureColour;
397             }
398           }
399           else
400           {
401           */
402           boolean drawn = renderFeature(g, seq, featureStartCol - 1,
403                   featureEndCol - 1, featureColour, start, end, y1,
404                   colourOnly);
405           if (drawn)
406           {
407             drawnColour = featureColour;
408           }
409           /*}*/
410         }
411       }
412     }
413
414     if (transparency != 1.0f)
415     {
416       /*
417        * reset transparency
418        */
419       ((Graphics2D) g).setComposite(NO_TRANSPARENCY);
420     }
421
422     return drawnColour;
423   }
424
425   /**
426    * Called when alignment in associated view has new/modified features to
427    * discover and display.
428    * 
429    */
430   @Override
431   public void featuresAdded()
432   {
433     findAllFeatures();
434   }
435
436   /**
437    * Returns the sequence feature colour rendered at the given column position,
438    * or null if none found. The feature of highest render order (i.e. on top) is
439    * found, subject to both feature type and feature group being visible, and
440    * its colour returned. This method is suitable when no feature transparency
441    * applied (only the topmost visible feature colour is rendered).
442    * <p>
443    * Note this method does not check for a gap in the column so would return the
444    * colour for features enclosing a gapped column. Check for gap before calling
445    * if different behaviour is wanted.
446    * 
447    * @param seq
448    * @param column
449    *          (1..)
450    * @return
451    */
452   private Color findFeatureColour(SequenceI seq, int column)
453   {
454     /*
455      * check for new feature added while processing
456      */
457     updateFeatures();
458
459     /*
460      * inspect features in reverse renderOrder (the last in the array is 
461      * displayed on top) until we find one that is rendered at the position
462      */
463     for (int renderIndex = renderOrder.length; --renderIndex >= 0;)
464     {
465       String type = renderOrder[renderIndex];
466       if (!seq.hasFeatures(type) || !showFeatureOfType(type))
467       {
468         continue;
469       }
470
471       /*
472        * field overlaps is used by JalviewJS to avoid object creation;
473        * not thread-safe for Java (Javascript is single-threaded)
474        */
475       if (overlaps != null)
476       {
477         overlaps.clear();
478       }
479       List<SequenceFeature> list = seq.findFeatures(column, type, overlaps);
480       if (list.size() > 0)
481       {
482         for (int i = 0, n = list.size(); i < n; i++)
483         {
484           SequenceFeature sf = list.get(i);
485           if (featureGroupNotShown(sf))
486           {
487             continue;
488           }
489           Color col = getColour(sf);
490           if (col != null)
491           {
492             return col;
493           }
494         }
495       }
496     }
497
498     /*
499      * no displayed feature found at position
500      */
501     return null;
502   }
503 }