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