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