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