4498f881dd3689751d9132be8dcfaf36a5e6791c
[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.api.AlignViewportI;
24 import jalview.datamodel.SequenceGroup;
25 import jalview.datamodel.SequenceI;
26 import jalview.renderer.ResidueShaderI;
27 import jalview.renderer.seqfeatures.FeatureColourFinder;
28 import jalview.util.Comparison;
29
30 import java.awt.Color;
31 import java.awt.FontMetrics;
32 import java.awt.Graphics;
33
34 public class SequenceRenderer implements jalview.api.SequenceRenderer
35 {
36   final static int CHAR_TO_UPPER = 'A' - 'a';
37
38   AlignViewportI av;
39
40   FontMetrics fm;
41
42   boolean renderGaps = true;
43
44   SequenceGroup currentSequenceGroup = null;
45
46   SequenceGroup[] allGroups = null;
47
48   Color resBoxColour;
49
50   Graphics graphics;
51
52   boolean monospacedFont;
53
54   boolean forOverview = false;
55
56   /**
57    * Creates a new SequenceRenderer object
58    * 
59    * @param viewport
60    */
61   public SequenceRenderer(AlignViewportI viewport)
62   {
63     this.av = viewport;
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()
81             && av.getCharWidth() == dwidth);
82
83     this.renderGaps = renderGaps;
84   }
85
86   protected Color getResidueBoxColour(SequenceI seq, int i)
87   {
88     // rate limiting step when rendering overview for lots of groups
89     allGroups = av.getAlignment().findAllGroups(seq);
90
91     if (inCurrentSequenceGroup(i))
92     {
93       if (currentSequenceGroup.getDisplayBoxes())
94       {
95         getBoxColour(currentSequenceGroup.getGroupColourScheme(), seq, i);
96       }
97     }
98     else if (av.getShowBoxes())
99     {
100       getBoxColour(av.getResidueShading(), seq, i);
101     }
102
103     return resBoxColour;
104   }
105
106   /**
107    * Get the residue colour at the given sequence position - as determined by
108    * the sequence group colour (if any), else the colour scheme, possibly
109    * overridden by a feature colour.
110    * 
111    * @param seq
112    * @param position
113    * @param finder
114    * @return
115    */
116   @Override
117   public Color getResidueColour(final SequenceI seq, int position,
118           FeatureColourFinder finder)
119   {
120     Color col = getResidueBoxColour(seq, position);
121
122     if (finder != null)
123     {
124       col = finder.findFeatureColour(col, seq, position);
125     }
126     return col;
127   }
128
129   /**
130    * DOCUMENT ME!
131    * 
132    * @param shader
133    *          DOCUMENT ME!
134    * @param seq
135    *          DOCUMENT ME!
136    * @param i
137    *          DOCUMENT ME!
138    */
139   void getBoxColour(ResidueShaderI shader, SequenceI seq, int i)
140   {
141     if (shader.getColourScheme() != null)
142     {
143       resBoxColour = shader.findColour(seq.getCharAt(i), 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.isValidCharWidth())
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(),
220             avHeight = av.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(
311                 seq.getSequenceAsString(start, end + 1).replace(gap, ' '),
312                 0, y1);
313       }
314     }
315     else
316     {
317       boolean srep = av.isDisplayReferenceSeq();
318       boolean getboxColour = false;
319       boolean isarep = av.getAlignment().getSeqrep() == seq;
320       boolean isgrep = currentSequenceGroup != null
321               ? currentSequenceGroup.getSeqrep() == seq
322               : false;
323       char sr_c;
324       for (int i = start; i <= end; i++)
325       {
326
327         graphics.setColor(av.getTextColour());
328         getboxColour = false;
329         s = seq.getCharAt(i);
330
331         if (!renderGaps && jalview.util.Comparison.isGap(s))
332         {
333           continue;
334         }
335
336         if (inCurrentSequenceGroup(i))
337         {
338           if (!currentSequenceGroup.getDisplayText())
339           {
340             continue;
341           }
342
343           if (currentSequenceGroup.thresholdTextColour > 0
344                   || currentSequenceGroup.getColourText())
345           {
346             getboxColour = true;
347             getBoxColour(currentSequenceGroup.getGroupColourScheme(), seq,
348                     i);
349
350             if (currentSequenceGroup.getColourText())
351             {
352               graphics.setColor(resBoxColour.darker());
353             }
354
355             if (currentSequenceGroup.thresholdTextColour > 0)
356             {
357               if (resBoxColour.getRed() + resBoxColour.getBlue()
358                       + resBoxColour
359                               .getGreen() < currentSequenceGroup.thresholdTextColour)
360               {
361                 graphics.setColor(currentSequenceGroup.textColour2);
362               }
363             }
364           }
365           else
366           {
367             graphics.setColor(currentSequenceGroup.textColour);
368           }
369           if (!isarep && !isgrep
370                   && currentSequenceGroup.getShowNonconserved()) // todo
371                                                                  // optimize
372           {
373             // todo - use sequence group consensus
374             s = getDisplayChar(srep, i, s, '.', currentSequenceGroup);
375
376           }
377
378         }
379         else
380         {
381           if (!av.getShowText())
382           {
383             continue;
384           }
385
386           if (av.getColourText())
387           {
388             getboxColour = true;
389             getBoxColour(av.getResidueShading(), seq, i);
390
391             if (av.getShowBoxes())
392             {
393               graphics.setColor(resBoxColour.darker());
394             }
395             else
396             {
397               graphics.setColor(resBoxColour);
398             }
399           }
400
401           if (av.getThresholdTextColour() > 0)
402           {
403             if (!getboxColour)
404             {
405               getBoxColour(av.getResidueShading(), seq, i);
406             }
407
408             if (resBoxColour.getRed() + resBoxColour.getBlue()
409                     + resBoxColour.getGreen() < av.getThresholdTextColour())
410             {
411               graphics.setColor(av.getTextColour2());
412             }
413           }
414           if (!isarep && av.getShowUnconserved())
415           {
416             s = getDisplayChar(srep, i, s, '.', null);
417
418           }
419
420         }
421
422         charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
423         graphics.drawString(String.valueOf(s),
424                 charOffset + av.getCharWidth() * (i - start), y1);
425
426       }
427     }
428   }
429
430   /**
431    * Returns 'conservedChar' to represent the given position if the sequence
432    * character at that position is equal to the consensus (ignoring case), else
433    * returns the sequence character
434    * 
435    * @param usesrep
436    * @param position
437    * @param sequenceChar
438    * @param conservedChar
439    * @return
440    */
441   private char getDisplayChar(final boolean usesrep, int position,
442           char sequenceChar, char conservedChar, SequenceGroup currentGroup)
443   {
444     // TODO - use currentSequenceGroup rather than alignment
445     // currentSequenceGroup.getConsensus()
446     char conschar = (usesrep) ? (currentGroup == null
447             || position < currentGroup.getStartRes()
448             || position > currentGroup.getEndRes()
449                     ? av.getAlignment().getSeqrep().getCharAt(position)
450                     : (currentGroup.getSeqrep() != null
451                             ? currentGroup.getSeqrep().getCharAt(position)
452                             : av.getAlignment().getSeqrep()
453                                     .getCharAt(position)))
454             : (currentGroup != null && currentGroup.getConsensus() != null
455                     && position >= currentGroup.getStartRes()
456                     && position <= currentGroup.getEndRes()
457                     && currentGroup
458                             .getConsensus().annotations.length > position)
459                                     ? currentGroup
460                                             .getConsensus().annotations[position].displayCharacter
461                                                     .charAt(0)
462                                     : av.getAlignmentConsensusAnnotation().annotations[position].displayCharacter
463                                             .charAt(0);
464     if (!jalview.util.Comparison.isGap(conschar)
465             && (sequenceChar == conschar
466                     || sequenceChar + CHAR_TO_UPPER == conschar))
467     {
468       sequenceChar = conservedChar;
469     }
470     return sequenceChar;
471   }
472
473   /**
474    * DOCUMENT ME!
475    * 
476    * @param res
477    *          DOCUMENT ME!
478    * 
479    * @return DOCUMENT ME!
480    */
481   boolean inCurrentSequenceGroup(int res)
482   {
483     if (allGroups == null)
484     {
485       return false;
486     }
487
488     for (int i = 0; i < allGroups.length; i++)
489     {
490       if ((allGroups[i].getStartRes() <= res)
491               && (allGroups[i].getEndRes() >= res))
492       {
493         currentSequenceGroup = allGroups[i];
494
495         return true;
496       }
497     }
498
499     return false;
500   }
501
502   /**
503    * DOCUMENT ME!
504    * 
505    * @param seq
506    *          DOCUMENT ME!
507    * @param start
508    *          DOCUMENT ME!
509    * @param end
510    *          DOCUMENT ME!
511    * @param x1
512    *          DOCUMENT ME!
513    * @param y1
514    *          DOCUMENT ME!
515    * @param width
516    *          DOCUMENT ME!
517    * @param height
518    *          DOCUMENT ME!
519    */
520   public void drawHighlightedText(SequenceI seq, int start, int end, int x1,
521           int y1)
522   {
523     int pady = av.getCharHeight() / 5;
524     int charOffset = 0;
525     graphics.setColor(Color.BLACK);
526     graphics.fillRect(x1, y1, av.getCharWidth() * (end - start + 1),
527             av.getCharHeight());
528     graphics.setColor(Color.white);
529
530     char s = '~';
531
532     // Need to find the sequence position here.
533     if (av.isValidCharWidth())
534     {
535       for (int i = start; i <= end; i++)
536       {
537         if (i < seq.getLength())
538         {
539           s = seq.getCharAt(i);
540         }
541
542         charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
543         graphics.drawString(String.valueOf(s),
544                 charOffset + x1 + (av.getCharWidth() * (i - start)),
545                 (y1 + av.getCharHeight()) - pady);
546       }
547     }
548   }
549
550   public void drawCursor(SequenceI seq, int res, int x1, int y1)
551   {
552     int pady = av.getCharHeight() / 5;
553     int charOffset = 0;
554     graphics.setColor(Color.black);
555     graphics.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight());
556
557     if (av.isValidCharWidth())
558     {
559       graphics.setColor(Color.white);
560
561       char s = seq.getCharAt(res);
562
563       charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
564       graphics.drawString(String.valueOf(s), charOffset + x1,
565               (y1 + av.getCharHeight()) - pady);
566     }
567
568   }
569 }