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