Merge branch 'develop' into groovy/JAL-2074_upgradeto2.4.4
[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       boolean isarep = av.getAlignment().getSeqrep() == seq;
327       boolean isgrep = currentSequenceGroup != null ? currentSequenceGroup
328               .getSeqrep() == seq : false;
329       char sr_c;
330       for (int i = start; i <= end; i++)
331       {
332
333         graphics.setColor(av.getTextColour());
334         getboxColour = false;
335         s = seq.getCharAt(i);
336
337         if (!renderGaps && jalview.util.Comparison.isGap(s))
338         {
339           continue;
340         }
341
342         if (inCurrentSequenceGroup(i))
343         {
344           if (!currentSequenceGroup.getDisplayText())
345           {
346             continue;
347           }
348
349           if (currentSequenceGroup.thresholdTextColour > 0
350                   || currentSequenceGroup.getColourText())
351           {
352             getboxColour = true;
353             getBoxColour(currentSequenceGroup.cs, seq, i);
354
355             if (currentSequenceGroup.getColourText())
356             {
357               graphics.setColor(resBoxColour.darker());
358             }
359
360             if (currentSequenceGroup.thresholdTextColour > 0)
361             {
362               if (resBoxColour.getRed() + resBoxColour.getBlue()
363                       + resBoxColour.getGreen() < currentSequenceGroup.thresholdTextColour)
364               {
365                 graphics.setColor(currentSequenceGroup.textColour2);
366               }
367             }
368           }
369           else
370           {
371             graphics.setColor(currentSequenceGroup.textColour);
372           }
373           if (!isarep && !isgrep
374                   && currentSequenceGroup.getShowNonconserved()) // todo
375                                                                  // optimize
376           {
377             // todo - use sequence group consensus
378             s = getDisplayChar(srep, i, s, '.', currentSequenceGroup);
379
380           }
381
382         }
383         else
384         {
385           if (!av.getShowText())
386           {
387             continue;
388           }
389
390           if (av.getColourText())
391           {
392             getboxColour = true;
393             getBoxColour(av.getGlobalColourScheme(), seq, i);
394
395             if (av.getShowBoxes())
396             {
397               graphics.setColor(resBoxColour.darker());
398             }
399             else
400             {
401               graphics.setColor(resBoxColour);
402             }
403           }
404
405           if (av.getThresholdTextColour() > 0)
406           {
407             if (!getboxColour)
408             {
409               getBoxColour(av.getGlobalColourScheme(), seq, i);
410             }
411
412             if (resBoxColour.getRed() + resBoxColour.getBlue()
413                     + resBoxColour.getGreen() < av.getThresholdTextColour())
414             {
415               graphics.setColor(av.getTextColour2());
416             }
417           }
418           if (!isarep && av.getShowUnconserved())
419           {
420             s = getDisplayChar(srep, i, s, '.', currentSequenceGroup);
421
422           }
423
424         }
425
426         charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
427         graphics.drawString(String.valueOf(s),
428                 charOffset + av.getCharWidth() * (i - start), y1);
429
430       }
431     }
432   }
433
434   /**
435    * Returns 'conservedChar' to represent the given position if the sequence
436    * character at that position is equal to the consensus (ignoring case), else
437    * returns the sequence character
438    * 
439    * @param usesrep
440    * @param position
441    * @param sequenceChar
442    * @param conservedChar
443    * @return
444    */
445   private char getDisplayChar(final boolean usesrep, int position,
446           char sequenceChar, char conservedChar, SequenceGroup currentGroup)
447   {
448     // TODO - use currentSequenceGroup rather than alignment
449     // currentSequenceGroup.getConsensus()
450     char conschar = (usesrep) ? (currentGroup == null ? 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) ? currentGroup
456                     .getConsensus().annotations[position].displayCharacter
457                     .charAt(0)
458                     : av.getAlignmentConsensusAnnotation().annotations[position].displayCharacter
459                             .charAt(0);
460     if (!jalview.util.Comparison.isGap(conschar)
461             && (sequenceChar == conschar || sequenceChar + CHAR_TO_UPPER == conschar))
462     {
463       sequenceChar = conservedChar;
464     }
465     return sequenceChar;
466   }
467
468   /**
469    * DOCUMENT ME!
470    * 
471    * @param res
472    *          DOCUMENT ME!
473    * 
474    * @return DOCUMENT ME!
475    */
476   boolean inCurrentSequenceGroup(int res)
477   {
478     if (allGroups == null)
479     {
480       return false;
481     }
482
483     for (int i = 0; i < allGroups.length; i++)
484     {
485       if ((allGroups[i].getStartRes() <= res)
486               && (allGroups[i].getEndRes() >= res))
487       {
488         currentSequenceGroup = allGroups[i];
489
490         return true;
491       }
492     }
493
494     return false;
495   }
496
497   /**
498    * DOCUMENT ME!
499    * 
500    * @param seq
501    *          DOCUMENT ME!
502    * @param start
503    *          DOCUMENT ME!
504    * @param end
505    *          DOCUMENT ME!
506    * @param x1
507    *          DOCUMENT ME!
508    * @param y1
509    *          DOCUMENT ME!
510    * @param width
511    *          DOCUMENT ME!
512    * @param height
513    *          DOCUMENT ME!
514    */
515   public void drawHighlightedText(SequenceI seq, int start, int end,
516           int x1, int y1)
517   {
518     int pady = av.getCharHeight() / 5;
519     int charOffset = 0;
520     graphics.setColor(Color.BLACK);
521     graphics.fillRect(x1, y1, av.getCharWidth() * (end - start + 1),
522             av.getCharHeight());
523     graphics.setColor(Color.white);
524
525     char s = '~';
526
527     // Need to find the sequence position here.
528     if (av.isValidCharWidth())
529     {
530       for (int i = start; i <= end; i++)
531       {
532         if (i < seq.getLength())
533         {
534           s = seq.getCharAt(i);
535         }
536
537         charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
538         graphics.drawString(String.valueOf(s),
539                 charOffset + x1 + (av.getCharWidth() * (i - start)),
540                 (y1 + av.getCharHeight()) - pady);
541       }
542     }
543   }
544
545   public void drawCursor(SequenceI seq, int res, int x1, int y1)
546   {
547     int pady = av.getCharHeight() / 5;
548     int charOffset = 0;
549     graphics.setColor(Color.black);
550     graphics.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight());
551
552     if (av.isValidCharWidth())
553     {
554       graphics.setColor(Color.white);
555
556       char s = seq.getCharAt(res);
557
558       charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
559       graphics.drawString(String.valueOf(s), charOffset + x1,
560               (y1 + av.getCharHeight()) - pady);
561     }
562
563   }
564 }