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