JAL-2089 patch broken merge to master for Release 2.10.0b1
[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.datamodel.SequenceFeature;
25 import jalview.datamodel.SequenceI;
26 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
27
28 import java.awt.AlphaComposite;
29 import java.awt.Color;
30 import java.awt.FontMetrics;
31 import java.awt.Graphics;
32 import java.awt.Graphics2D;
33 import java.awt.image.BufferedImage;
34
35 public class FeatureRenderer extends FeatureRendererModel
36 {
37
38   FontMetrics fm;
39
40   int charOffset;
41
42   boolean offscreenRender = false;
43
44   protected SequenceI lastSeq;
45
46   char s;
47
48   int i;
49
50   int av_charHeight, av_charWidth;
51
52   boolean av_validCharWidth, av_isShowSeqFeatureHeight;
53
54   private Integer currentColour;
55
56   /**
57    * Constructor given a viewport
58    * 
59    * @param viewport
60    */
61   public FeatureRenderer(AlignViewportI viewport)
62   {
63     this.av = viewport;
64   }
65
66   protected void updateAvConfig()
67   {
68     av_charHeight = av.getCharHeight();
69     av_charWidth = av.getCharWidth();
70     av_validCharWidth = av.isValidCharWidth();
71     av_isShowSeqFeatureHeight = av.isShowSequenceFeaturesHeight();
72   }
73
74   void renderFeature(Graphics g, SequenceI seq, int fstart, int fend,
75           Color featureColour, int start, int end, int y1)
76   {
77     updateAvConfig();
78     if (((fstart <= end) && (fend >= start)))
79     {
80       if (fstart < start)
81       { // fix for if the feature we have starts before the sequence start,
82         fstart = start; // but the feature end is still valid!!
83       }
84
85       if (fend >= end)
86       {
87         fend = end;
88       }
89       int pady = (y1 + av_charHeight) - av_charHeight / 5;
90       for (i = fstart; i <= fend; i++)
91       {
92         s = seq.getCharAt(i);
93
94         if (jalview.util.Comparison.isGap(s))
95         {
96           continue;
97         }
98
99         g.setColor(featureColour);
100
101         g.fillRect((i - start) * av_charWidth, y1, av_charWidth,
102                 av_charHeight);
103
104         if (offscreenRender || !av_validCharWidth)
105         {
106           continue;
107         }
108
109         g.setColor(Color.white);
110         charOffset = (av_charWidth - fm.charWidth(s)) / 2;
111         g.drawString(String.valueOf(s), charOffset
112                 + (av_charWidth * (i - start)), pady);
113
114       }
115     }
116   }
117
118   void renderScoreFeature(Graphics g, SequenceI seq, int fstart, int fend,
119           Color featureColour, int start, int end, int y1, byte[] bs)
120   {
121     updateAvConfig();
122     if (((fstart <= end) && (fend >= start)))
123     {
124       if (fstart < start)
125       { // fix for if the feature we have starts before the sequence start,
126         fstart = start; // but the feature end is still valid!!
127       }
128
129       if (fend >= end)
130       {
131         fend = end;
132       }
133       int pady = (y1 + av_charHeight) - av_charHeight / 5;
134       int ystrt = 0, yend = av_charHeight;
135       if (bs[0] != 0)
136       {
137         // signed - zero is always middle of residue line.
138         if (bs[1] < 128)
139         {
140           yend = av_charHeight * (128 - bs[1]) / 512;
141           ystrt = av_charHeight - yend / 2;
142         }
143         else
144         {
145           ystrt = av_charHeight / 2;
146           yend = av_charHeight * (bs[1] - 128) / 512;
147         }
148       }
149       else
150       {
151         yend = av_charHeight * bs[1] / 255;
152         ystrt = av_charHeight - yend;
153
154       }
155       for (i = fstart; i <= fend; i++)
156       {
157         s = seq.getCharAt(i);
158
159         if (jalview.util.Comparison.isGap(s))
160         {
161           continue;
162         }
163
164         g.setColor(featureColour);
165         int x = (i - start) * av_charWidth;
166         g.drawRect(x, y1, av_charWidth, av_charHeight);
167         g.fillRect(x, y1 + ystrt, av_charWidth, yend);
168
169         if (offscreenRender || !av_validCharWidth)
170         {
171           continue;
172         }
173
174         g.setColor(Color.black);
175         charOffset = (av_charWidth - fm.charWidth(s)) / 2;
176         g.drawString(String.valueOf(s), charOffset
177                 + (av_charWidth * (i - start)), pady);
178       }
179     }
180   }
181
182   BufferedImage offscreenImage;
183
184   @Override
185   public Color findFeatureColour(Color initialCol, SequenceI seq, int res)
186   {
187     return new Color(findFeatureColour(initialCol.getRGB(), seq, res));
188   }
189
190   /**
191    * This is used by the Molecule Viewer and Overview to get the accurate colour
192    * of the rendered sequence
193    */
194   public synchronized int findFeatureColour(int initialCol,
195           final SequenceI seq, int column)
196   {
197     if (!av.isShowSequenceFeatures())
198     {
199       return initialCol;
200     }
201
202     SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
203     if (seq != lastSeq)
204     {
205       lastSeq = seq;
206       lastSequenceFeatures = sequenceFeatures;
207       if (lastSequenceFeatures != null)
208       {
209         sfSize = lastSequenceFeatures.length;
210       }
211     }
212     else
213     {
214       if (lastSequenceFeatures != sequenceFeatures)
215       {
216         lastSequenceFeatures = sequenceFeatures;
217         if (lastSequenceFeatures != null)
218         {
219           sfSize = lastSequenceFeatures.length;
220         }
221       }
222     }
223
224     if (lastSequenceFeatures == null || sfSize == 0)
225     {
226       return initialCol;
227     }
228
229     if (jalview.util.Comparison.isGap(lastSeq.getCharAt(column)))
230     {
231       return Color.white.getRGB();
232     }
233
234     // Only bother making an offscreen image if transparency is applied
235     if (transparency != 1.0f && offscreenImage == null)
236     {
237       offscreenImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
238     }
239
240     currentColour = null;
241     // TODO: non-threadsafe - each rendering thread needs its own instance of
242     // the feature renderer - or this should be synchronized.
243     offscreenRender = true;
244
245     if (offscreenImage != null)
246     {
247       offscreenImage.setRGB(0, 0, initialCol);
248       drawSequence(offscreenImage.getGraphics(), lastSeq, column, column, 0);
249
250       return offscreenImage.getRGB(0, 0);
251     }
252     else
253     {
254       drawSequence(null, lastSeq, lastSeq.findPosition(column), -1, -1);
255
256       if (currentColour == null)
257       {
258         return initialCol;
259       }
260       else
261       {
262         return currentColour.intValue();
263       }
264     }
265
266   }
267
268   private volatile SequenceFeature[] lastSequenceFeatures;
269
270   int sfSize;
271
272   int sfindex;
273
274   int spos;
275
276   int epos;
277
278   public synchronized void drawSequence(Graphics g, final SequenceI seq,
279           int start, int end, int y1)
280   {
281     SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
282     if (sequenceFeatures == null || sequenceFeatures.length == 0)
283     {
284       return;
285     }
286
287     if (g != null)
288     {
289       fm = g.getFontMetrics();
290     }
291
292     updateFeatures();
293
294     if (lastSeq == null || seq != lastSeq
295             || sequenceFeatures != lastSequenceFeatures)
296     {
297       lastSeq = seq;
298       lastSequenceFeatures = sequenceFeatures;
299     }
300
301     if (transparency != 1 && g != null)
302     {
303       Graphics2D g2 = (Graphics2D) g;
304       g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
305               transparency));
306     }
307
308     if (!offscreenRender)
309     {
310       spos = lastSeq.findPosition(start);
311       epos = lastSeq.findPosition(end);
312     }
313
314     sfSize = lastSequenceFeatures.length;
315     String type;
316     for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
317     {
318       type = renderOrder[renderIndex];
319
320       if (type == null || !showFeatureOfType(type))
321       {
322         continue;
323       }
324
325       // loop through all features in sequence to find
326       // current feature to render
327       for (sfindex = 0; sfindex < sfSize; sfindex++)
328       {
329         final SequenceFeature sequenceFeature = lastSequenceFeatures[sfindex];
330         if (!sequenceFeature.type.equals(type))
331         {
332           continue;
333         }
334
335         if (featureGroups != null
336                 && sequenceFeature.featureGroup != null
337                 && sequenceFeature.featureGroup.length() != 0
338                 && featureGroups.containsKey(sequenceFeature.featureGroup)
339                 && !featureGroups.get(sequenceFeature.featureGroup)
340                         .booleanValue())
341         {
342           continue;
343         }
344
345         if (!offscreenRender
346                 && (sequenceFeature.getBegin() > epos || sequenceFeature
347                         .getEnd() < spos))
348         {
349           continue;
350         }
351
352         if (offscreenRender && offscreenImage == null)
353         {
354           if (sequenceFeature.begin <= start
355                   && sequenceFeature.end >= start)
356           {
357             // this is passed out to the overview and other sequence renderers
358             // (e.g. molecule viewer) to get displayed colour for rendered
359             // sequence
360             currentColour = new Integer(getColour(sequenceFeature).getRGB());
361             // used to be retreived from av.featuresDisplayed
362             // currentColour = av.featuresDisplayed
363             // .get(sequenceFeatures[sfindex].type);
364
365           }
366         }
367         else if (sequenceFeature.type.equals("disulfide bond"))
368         {
369           renderFeature(g, seq, seq.findIndex(sequenceFeature.begin) - 1,
370                   seq.findIndex(sequenceFeature.begin) - 1,
371                   getColour(sequenceFeature)
372                   // new Color(((Integer) av.featuresDisplayed
373                   // .get(sequenceFeatures[sfindex].type)).intValue())
374                   , start, end, y1);
375           renderFeature(g, seq, seq.findIndex(sequenceFeature.end) - 1,
376                   seq.findIndex(sequenceFeature.end) - 1,
377                   getColour(sequenceFeature)
378                   // new Color(((Integer) av.featuresDisplayed
379                   // .get(sequenceFeatures[sfindex].type)).intValue())
380                   , start, end, y1);
381
382         }
383         else if (showFeature(sequenceFeature))
384         {
385           if (av_isShowSeqFeatureHeight
386                   && !Float.isNaN(sequenceFeature.score))
387           {
388             renderScoreFeature(g, seq,
389                     seq.findIndex(sequenceFeature.begin) - 1,
390                     seq.findIndex(sequenceFeature.end) - 1,
391                     getColour(sequenceFeature), start, end, y1,
392                     normaliseScore(sequenceFeature));
393           }
394           else
395           {
396             renderFeature(g, seq, seq.findIndex(sequenceFeature.begin) - 1,
397                     seq.findIndex(sequenceFeature.end) - 1,
398                     getColour(sequenceFeature), start, end, y1);
399           }
400         }
401
402       }
403
404     }
405
406     if (transparency != 1.0f && g != null)
407     {
408       Graphics2D g2 = (Graphics2D) g;
409       g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
410               1.0f));
411     }
412   }
413
414   /**
415    * Called when alignment in associated view has new/modified features to
416    * discover and display.
417    * 
418    */
419   @Override
420   public void featuresAdded()
421   {
422     lastSeq = null;
423     findAllFeatures();
424   }
425 }