JAL-2438 FeatureColourFinder refactored from FeatureRenderer, fr not
[jalview.git] / src / jalview / gui / SequenceRenderer.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.gui;
22
23 import jalview.datamodel.SequenceGroup;
24 import jalview.datamodel.SequenceI;
25 import jalview.renderer.ResidueShaderI;
26 import jalview.renderer.seqfeatures.FeatureColourFinder;
27 import jalview.util.Comparison;
28
29 import java.awt.Color;
30 import java.awt.FontMetrics;
31 import java.awt.Graphics;
32
33 public class SequenceRenderer implements jalview.api.SequenceRenderer
34 {
35   final static int CHAR_TO_UPPER = 'A' - 'a';
36
37   AlignViewport av;
38
39   FontMetrics fm;
40
41   boolean renderGaps = true;
42
43   SequenceGroup currentSequenceGroup = null;
44
45   SequenceGroup[] allGroups = null;
46
47   Color resBoxColour;
48
49   Graphics graphics;
50
51   boolean monospacedFont;
52
53   boolean forOverview = false;
54
55   /**
56    * Creates a new SequenceRenderer object.
57    * 
58    * @param av
59    *          DOCUMENT ME!
60    */
61   public SequenceRenderer(AlignViewport av)
62   {
63     this.av = av;
64   }
65
66   /**
67    * DOCUMENT ME!
68    * 
69    * @param b
70    *          DOCUMENT ME!
71    */
72   public void prepare(Graphics g, boolean renderGaps)
73   {
74     graphics = g;
75     fm = g.getFontMetrics();
76
77     // If EPS graphics, stringWidth will be a double, not an int
78     double dwidth = fm.getStringBounds("M", g).getWidth();
79
80     monospacedFont = (dwidth == fm.getStringBounds("|", g).getWidth() && av
81             .getCharWidth() == dwidth);
82
83     this.renderGaps = renderGaps;
84   }
85
86   @Override
87   public Color getResidueBoxColour(SequenceI seq, int i)
88   {
89     // rate limiting step when rendering overview for lots of groups
90     allGroups = av.getAlignment().findAllGroups(seq);
91
92     if (inCurrentSequenceGroup(i))
93     {
94       if (currentSequenceGroup.getDisplayBoxes())
95       {
96         getBoxColour(currentSequenceGroup.getGroupColourScheme(), seq, i);
97       }
98     }
99     else if (av.getShowBoxes())
100     {
101       getBoxColour(av.getResidueShading(), seq, i);
102     }
103
104     return resBoxColour;
105   }
106
107   /**
108    * Get the residue colour at the given sequence position - as determined by
109    * the sequence group colour (if any), else the colour scheme, possibly
110    * overridden by a feature colour.
111    * 
112    * @param seq
113    * @param position
114    * @param finder
115    * @return
116    */
117   @Override
118   public Color getResidueColour(final SequenceI seq, int position,
119           FeatureColourFinder finder)
120   {
121     // TODO replace 8 or so code duplications with calls to this method
122     // (refactored as needed)
123     Color col = getResidueBoxColour(seq, position);
124
125     if (finder != null)
126     {
127       col = finder.findFeatureColour(col, seq, position);
128     }
129     return col;
130   }
131
132   /**
133    * DOCUMENT ME!
134    * 
135    * @param shader
136    *          DOCUMENT ME!
137    * @param seq
138    *          DOCUMENT ME!
139    * @param i
140    *          DOCUMENT ME!
141    */
142   void getBoxColour(ResidueShaderI shader, SequenceI seq, int i)
143   {
144     if (shader != null)
145     {
146       resBoxColour = shader.findColour(seq.getCharAt(i),
147               i, seq);
148     }
149     else if (forOverview && !Comparison.isGap(seq.getCharAt(i)))
150     {
151       resBoxColour = Color.lightGray;
152     }
153     else
154     {
155       resBoxColour = Color.white;
156     }
157   }
158
159   /**
160    * DOCUMENT ME!
161    * 
162    * @param g
163    *          DOCUMENT ME!
164    * @param seq
165    *          DOCUMENT ME!
166    * @param sg
167    *          DOCUMENT ME!
168    * @param start
169    *          DOCUMENT ME!
170    * @param end
171    *          DOCUMENT ME!
172    * @param x1
173    *          DOCUMENT ME!
174    * @param y1
175    *          DOCUMENT ME!
176    * @param width
177    *          DOCUMENT ME!
178    * @param height
179    *          DOCUMENT ME!
180    */
181   public void drawSequence(SequenceI seq, SequenceGroup[] sg, int start,
182           int end, int y1)
183   {
184     allGroups = sg;
185
186     drawBoxes(seq, start, end, y1);
187
188     if (av.validCharWidth)
189     {
190       drawText(seq, start, end, y1);
191     }
192   }
193
194   /**
195    * DOCUMENT ME!
196    * 
197    * @param seq
198    *          DOCUMENT ME!
199    * @param start
200    *          DOCUMENT ME!
201    * @param end
202    *          DOCUMENT ME!
203    * @param x1
204    *          DOCUMENT ME!
205    * @param y1
206    *          DOCUMENT ME!
207    * @param width
208    *          DOCUMENT ME!
209    * @param height
210    *          DOCUMENT ME!
211    */
212   public synchronized void drawBoxes(SequenceI seq, int start, int end,
213           int y1)
214   {
215     if (seq == null)
216     {
217       return; // fix for racecondition
218     }
219     int i = start;
220     int length = seq.getLength();
221
222     int curStart = -1;
223     int curWidth = av.getCharWidth(), avWidth = av.getCharWidth(), avHeight = av
224             .getCharHeight();
225
226     Color tempColour = null;
227
228     while (i <= end)
229     {
230       resBoxColour = Color.white;
231
232       if (i < length)
233       {
234         if (inCurrentSequenceGroup(i))
235         {
236           if (currentSequenceGroup.getDisplayBoxes())
237           {
238             getBoxColour(currentSequenceGroup.getGroupColourScheme(), seq,
239                     i);
240           }
241         }
242         else if (av.getShowBoxes())
243         {
244           getBoxColour(av.getResidueShading(), seq, i);
245         }
246       }
247
248       if (resBoxColour != tempColour)
249       {
250         if (tempColour != null)
251         {
252           graphics.fillRect(avWidth * (curStart - start), y1, curWidth,
253                   avHeight);
254         }
255
256         graphics.setColor(resBoxColour);
257
258         curStart = i;
259         curWidth = avWidth;
260         tempColour = resBoxColour;
261       }
262       else
263       {
264         curWidth += avWidth;
265       }
266
267       i++;
268     }
269
270     graphics.fillRect(avWidth * (curStart - start), y1, curWidth, avHeight);
271
272   }
273
274   /**
275    * DOCUMENT ME!
276    * 
277    * @param seq
278    *          DOCUMENT ME!
279    * @param start
280    *          DOCUMENT ME!
281    * @param end
282    *          DOCUMENT ME!
283    * @param x1
284    *          DOCUMENT ME!
285    * @param y1
286    *          DOCUMENT ME!
287    * @param width
288    *          DOCUMENT ME!
289    * @param height
290    *          DOCUMENT ME!
291    */
292   public void drawText(SequenceI seq, int start, int end, int y1)
293   {
294     y1 += av.getCharHeight() - av.getCharHeight() / 5; // height/5 replaces pady
295     int charOffset = 0;
296     char s;
297
298     if (end + 1 >= seq.getLength())
299     {
300       end = seq.getLength() - 1;
301     }
302     graphics.setColor(av.getTextColour());
303
304     if (monospacedFont && av.getShowText() && allGroups.length == 0
305             && !av.getColourText() && av.getThresholdTextColour() == 0)
306     {
307       if (av.isRenderGaps())
308       {
309         graphics.drawString(seq.getSequenceAsString(start, end + 1), 0, y1);
310       }
311       else
312       {
313         char gap = av.getGapCharacter();
314         graphics.drawString(seq.getSequenceAsString(start, end + 1)
315                 .replace(gap, ' '), 0, y1);
316       }
317     }
318     else
319     {
320       boolean srep = av.isDisplayReferenceSeq();
321       boolean getboxColour = false;
322       boolean isarep = av.getAlignment().getSeqrep() == seq;
323       boolean isgrep = currentSequenceGroup != null ? currentSequenceGroup
324               .getSeqrep() == seq : false;
325       char sr_c;
326       for (int i = start; i <= end; i++)
327       {
328
329         graphics.setColor(av.getTextColour());
330         getboxColour = false;
331         s = seq.getCharAt(i);
332
333         if (!renderGaps && jalview.util.Comparison.isGap(s))
334         {
335           continue;
336         }
337
338         if (inCurrentSequenceGroup(i))
339         {
340           if (!currentSequenceGroup.getDisplayText())
341           {
342             continue;
343           }
344
345           if (currentSequenceGroup.thresholdTextColour > 0
346                   || currentSequenceGroup.getColourText())
347           {
348             getboxColour = true;
349             getBoxColour(currentSequenceGroup.getGroupColourScheme(), seq,
350                     i);
351
352             if (currentSequenceGroup.getColourText())
353             {
354               graphics.setColor(resBoxColour.darker());
355             }
356
357             if (currentSequenceGroup.thresholdTextColour > 0)
358             {
359               if (resBoxColour.getRed() + resBoxColour.getBlue()
360                       + resBoxColour.getGreen() < currentSequenceGroup.thresholdTextColour)
361               {
362                 graphics.setColor(currentSequenceGroup.textColour2);
363               }
364             }
365           }
366           else
367           {
368             graphics.setColor(currentSequenceGroup.textColour);
369           }
370           if (!isarep && !isgrep
371                   && currentSequenceGroup.getShowNonconserved()) // todo
372                                                                  // optimize
373           {
374             // todo - use sequence group consensus
375             s = getDisplayChar(srep, i, s, '.', currentSequenceGroup);
376
377           }
378
379         }
380         else
381         {
382           if (!av.getShowText())
383           {
384             continue;
385           }
386
387           if (av.getColourText())
388           {
389             getboxColour = true;
390             getBoxColour(av.getResidueShading(), seq, i);
391
392             if (av.getShowBoxes())
393             {
394               graphics.setColor(resBoxColour.darker());
395             }
396             else
397             {
398               graphics.setColor(resBoxColour);
399             }
400           }
401
402           if (av.getThresholdTextColour() > 0)
403           {
404             if (!getboxColour)
405             {
406               getBoxColour(av.getResidueShading(), seq, i);
407             }
408
409             if (resBoxColour.getRed() + resBoxColour.getBlue()
410                     + resBoxColour.getGreen() < av.getThresholdTextColour())
411             {
412               graphics.setColor(av.getTextColour2());
413             }
414           }
415           if (!isarep && av.getShowUnconserved())
416           {
417             s = getDisplayChar(srep, i, s, '.', null);
418
419           }
420
421         }
422
423         charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
424         graphics.drawString(String.valueOf(s),
425                 charOffset + av.getCharWidth() * (i - start), y1);
426
427       }
428     }
429   }
430
431   /**
432    * Returns 'conservedChar' to represent the given position if the sequence
433    * character at that position is equal to the consensus (ignoring case), else
434    * returns the sequence character
435    * 
436    * @param usesrep
437    * @param position
438    * @param sequenceChar
439    * @param conservedChar
440    * @return
441    */
442   private char getDisplayChar(final boolean usesrep, int position,
443           char sequenceChar, char conservedChar, SequenceGroup currentGroup)
444   {
445     // TODO - use currentSequenceGroup rather than alignment
446     // currentSequenceGroup.getConsensus()
447     char conschar = (usesrep) ? (currentGroup == null
448             || position < currentGroup.getStartRes()
449             || position > currentGroup.getEndRes() ? av.getAlignment()
450             .getSeqrep().getCharAt(position)
451             : (currentGroup.getSeqrep() != null ? currentGroup.getSeqrep()
452                     .getCharAt(position) : av.getAlignment().getSeqrep()
453                     .getCharAt(position)))
454             : (currentGroup != null && currentGroup.getConsensus() != null
455                     && position >= currentGroup.getStartRes()
456                     && position <= currentGroup.getEndRes() && currentGroup
457                     .getConsensus().annotations.length > position) ? currentGroup
458                     .getConsensus().annotations[position].displayCharacter
459                     .charAt(0)
460                     : av.getAlignmentConsensusAnnotation().annotations[position].displayCharacter
461                             .charAt(0);
462     if (!jalview.util.Comparison.isGap(conschar)
463             && (sequenceChar == conschar || sequenceChar + CHAR_TO_UPPER == conschar))
464     {
465       sequenceChar = conservedChar;
466     }
467     return sequenceChar;
468   }
469
470   /**
471    * DOCUMENT ME!
472    * 
473    * @param res
474    *          DOCUMENT ME!
475    * 
476    * @return DOCUMENT ME!
477    */
478   boolean inCurrentSequenceGroup(int res)
479   {
480     if (allGroups == null)
481     {
482       return false;
483     }
484
485     for (int i = 0; i < allGroups.length; i++)
486     {
487       if ((allGroups[i].getStartRes() <= res)
488               && (allGroups[i].getEndRes() >= res))
489       {
490         currentSequenceGroup = allGroups[i];
491
492         return true;
493       }
494     }
495
496     return false;
497   }
498
499   /**
500    * DOCUMENT ME!
501    * 
502    * @param seq
503    *          DOCUMENT ME!
504    * @param start
505    *          DOCUMENT ME!
506    * @param end
507    *          DOCUMENT ME!
508    * @param x1
509    *          DOCUMENT ME!
510    * @param y1
511    *          DOCUMENT ME!
512    * @param width
513    *          DOCUMENT ME!
514    * @param height
515    *          DOCUMENT ME!
516    */
517   public void drawHighlightedText(SequenceI seq, int start, int end,
518           int x1, int y1)
519   {
520     int pady = av.getCharHeight() / 5;
521     int charOffset = 0;
522     graphics.setColor(Color.BLACK);
523     graphics.fillRect(x1, y1, av.getCharWidth() * (end - start + 1),
524             av.getCharHeight());
525     graphics.setColor(Color.white);
526
527     char s = '~';
528
529     // Need to find the sequence position here.
530     if (av.isValidCharWidth())
531     {
532       for (int i = start; i <= end; i++)
533       {
534         if (i < seq.getLength())
535         {
536           s = seq.getCharAt(i);
537         }
538
539         charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
540         graphics.drawString(String.valueOf(s),
541                 charOffset + x1 + (av.getCharWidth() * (i - start)),
542                 (y1 + av.getCharHeight()) - pady);
543       }
544     }
545   }
546
547   public void drawCursor(SequenceI seq, int res, int x1, int y1)
548   {
549     int pady = av.getCharHeight() / 5;
550     int charOffset = 0;
551     graphics.setColor(Color.black);
552     graphics.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight());
553
554     if (av.isValidCharWidth())
555     {
556       graphics.setColor(Color.white);
557
558       char s = seq.getCharAt(res);
559
560       charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
561       graphics.drawString(String.valueOf(s), charOffset + x1,
562               (y1 + av.getCharHeight()) - pady);
563     }
564
565   }
566 }