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