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