JAL-2483 JAL-2575 don't sort newly discovered features
[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       resBoxColour = shader.findColour(seq.getCharAt(i),
144               i, seq);
145     }
146     else if (forOverview && !Comparison.isGap(seq.getCharAt(i)))
147     {
148       resBoxColour = Color.lightGray;
149     }
150     else
151     {
152       resBoxColour = Color.white;
153     }
154   }
155
156   /**
157    * DOCUMENT ME!
158    * 
159    * @param g
160    *          DOCUMENT ME!
161    * @param seq
162    *          DOCUMENT ME!
163    * @param sg
164    *          DOCUMENT ME!
165    * @param start
166    *          DOCUMENT ME!
167    * @param end
168    *          DOCUMENT ME!
169    * @param x1
170    *          DOCUMENT ME!
171    * @param y1
172    *          DOCUMENT ME!
173    * @param width
174    *          DOCUMENT ME!
175    * @param height
176    *          DOCUMENT ME!
177    */
178   public void drawSequence(SequenceI seq, SequenceGroup[] sg, int start,
179           int end, int y1)
180   {
181     allGroups = sg;
182
183     drawBoxes(seq, start, end, y1);
184
185     if (av.isValidCharWidth())
186     {
187       drawText(seq, start, end, y1);
188     }
189   }
190
191   /**
192    * DOCUMENT ME!
193    * 
194    * @param seq
195    *          DOCUMENT ME!
196    * @param start
197    *          DOCUMENT ME!
198    * @param end
199    *          DOCUMENT ME!
200    * @param x1
201    *          DOCUMENT ME!
202    * @param y1
203    *          DOCUMENT ME!
204    * @param width
205    *          DOCUMENT ME!
206    * @param height
207    *          DOCUMENT ME!
208    */
209   public synchronized void drawBoxes(SequenceI seq, int start, int end,
210           int y1)
211   {
212     if (seq == null)
213     {
214       return; // fix for racecondition
215     }
216     int i = start;
217     int length = seq.getLength();
218
219     int curStart = -1;
220     int curWidth = av.getCharWidth(), avWidth = av.getCharWidth(), avHeight = av
221             .getCharHeight();
222
223     Color tempColour = null;
224
225     while (i <= end)
226     {
227       resBoxColour = Color.white;
228
229       if (i < length)
230       {
231         if (inCurrentSequenceGroup(i))
232         {
233           if (currentSequenceGroup.getDisplayBoxes())
234           {
235             getBoxColour(currentSequenceGroup.getGroupColourScheme(), seq,
236                     i);
237           }
238         }
239         else if (av.getShowBoxes())
240         {
241           getBoxColour(av.getResidueShading(), seq, i);
242         }
243       }
244
245       if (resBoxColour != tempColour)
246       {
247         if (tempColour != null)
248         {
249           graphics.fillRect(avWidth * (curStart - start), y1, curWidth,
250                   avHeight);
251         }
252
253         graphics.setColor(resBoxColour);
254
255         curStart = i;
256         curWidth = avWidth;
257         tempColour = resBoxColour;
258       }
259       else
260       {
261         curWidth += avWidth;
262       }
263
264       i++;
265     }
266
267     graphics.fillRect(avWidth * (curStart - start), y1, curWidth, avHeight);
268
269   }
270
271   /**
272    * DOCUMENT ME!
273    * 
274    * @param seq
275    *          DOCUMENT ME!
276    * @param start
277    *          DOCUMENT ME!
278    * @param end
279    *          DOCUMENT ME!
280    * @param x1
281    *          DOCUMENT ME!
282    * @param y1
283    *          DOCUMENT ME!
284    * @param width
285    *          DOCUMENT ME!
286    * @param height
287    *          DOCUMENT ME!
288    */
289   public void drawText(SequenceI seq, int start, int end, int y1)
290   {
291     y1 += av.getCharHeight() - av.getCharHeight() / 5; // height/5 replaces pady
292     int charOffset = 0;
293     char s;
294
295     if (end + 1 >= seq.getLength())
296     {
297       end = seq.getLength() - 1;
298     }
299     graphics.setColor(av.getTextColour());
300
301     if (monospacedFont && av.getShowText() && allGroups.length == 0
302             && !av.getColourText() && av.getThresholdTextColour() == 0)
303     {
304       if (av.isRenderGaps())
305       {
306         graphics.drawString(seq.getSequenceAsString(start, end + 1), 0, y1);
307       }
308       else
309       {
310         char gap = av.getGapCharacter();
311         graphics.drawString(seq.getSequenceAsString(start, end + 1)
312                 .replace(gap, ' '), 0, y1);
313       }
314     }
315     else
316     {
317       boolean srep = av.isDisplayReferenceSeq();
318       boolean getboxColour = false;
319       boolean isarep = av.getAlignment().getSeqrep() == seq;
320       boolean isgrep = currentSequenceGroup != null ? currentSequenceGroup
321               .getSeqrep() == seq : false;
322       char sr_c;
323       for (int i = start; i <= end; i++)
324       {
325
326         graphics.setColor(av.getTextColour());
327         getboxColour = false;
328         s = seq.getCharAt(i);
329
330         if (!renderGaps && jalview.util.Comparison.isGap(s))
331         {
332           continue;
333         }
334
335         if (inCurrentSequenceGroup(i))
336         {
337           if (!currentSequenceGroup.getDisplayText())
338           {
339             continue;
340           }
341
342           if (currentSequenceGroup.thresholdTextColour > 0
343                   || currentSequenceGroup.getColourText())
344           {
345             getboxColour = true;
346             getBoxColour(currentSequenceGroup.getGroupColourScheme(), seq,
347                     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 (!isarep && !isgrep
368                   && currentSequenceGroup.getShowNonconserved()) // todo
369                                                                  // optimize
370           {
371             // todo - use sequence group consensus
372             s = getDisplayChar(srep, i, s, '.', currentSequenceGroup);
373
374           }
375
376         }
377         else
378         {
379           if (!av.getShowText())
380           {
381             continue;
382           }
383
384           if (av.getColourText())
385           {
386             getboxColour = true;
387             getBoxColour(av.getResidueShading(), seq, i);
388
389             if (av.getShowBoxes())
390             {
391               graphics.setColor(resBoxColour.darker());
392             }
393             else
394             {
395               graphics.setColor(resBoxColour);
396             }
397           }
398
399           if (av.getThresholdTextColour() > 0)
400           {
401             if (!getboxColour)
402             {
403               getBoxColour(av.getResidueShading(), seq, i);
404             }
405
406             if (resBoxColour.getRed() + resBoxColour.getBlue()
407                     + resBoxColour.getGreen() < av.getThresholdTextColour())
408             {
409               graphics.setColor(av.getTextColour2());
410             }
411           }
412           if (!isarep && av.getShowUnconserved())
413           {
414             s = getDisplayChar(srep, i, s, '.', null);
415
416           }
417
418         }
419
420         charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
421         graphics.drawString(String.valueOf(s),
422                 charOffset + av.getCharWidth() * (i - start), y1);
423
424       }
425     }
426   }
427
428   /**
429    * Returns 'conservedChar' to represent the given position if the sequence
430    * character at that position is equal to the consensus (ignoring case), else
431    * returns the sequence character
432    * 
433    * @param usesrep
434    * @param position
435    * @param sequenceChar
436    * @param conservedChar
437    * @return
438    */
439   private char getDisplayChar(final boolean usesrep, int position,
440           char sequenceChar, char conservedChar, SequenceGroup currentGroup)
441   {
442     // TODO - use currentSequenceGroup rather than alignment
443     // currentSequenceGroup.getConsensus()
444     char conschar = (usesrep) ? (currentGroup == null
445             || position < currentGroup.getStartRes()
446             || position > currentGroup.getEndRes() ? av.getAlignment()
447             .getSeqrep().getCharAt(position)
448             : (currentGroup.getSeqrep() != null ? currentGroup.getSeqrep()
449                     .getCharAt(position) : av.getAlignment().getSeqrep()
450                     .getCharAt(position)))
451             : (currentGroup != null && currentGroup.getConsensus() != null
452                     && position >= currentGroup.getStartRes()
453                     && position <= currentGroup.getEndRes() && currentGroup
454                     .getConsensus().annotations.length > position) ? currentGroup
455                     .getConsensus().annotations[position].displayCharacter
456                     .charAt(0)
457                     : av.getAlignmentConsensusAnnotation().annotations[position].displayCharacter
458                             .charAt(0);
459     if (!jalview.util.Comparison.isGap(conschar)
460             && (sequenceChar == conschar || sequenceChar + CHAR_TO_UPPER == conschar))
461     {
462       sequenceChar = conservedChar;
463     }
464     return sequenceChar;
465   }
466
467   /**
468    * DOCUMENT ME!
469    * 
470    * @param res
471    *          DOCUMENT ME!
472    * 
473    * @return DOCUMENT ME!
474    */
475   boolean inCurrentSequenceGroup(int res)
476   {
477     if (allGroups == null)
478     {
479       return false;
480     }
481
482     for (int i = 0; i < allGroups.length; i++)
483     {
484       if ((allGroups[i].getStartRes() <= res)
485               && (allGroups[i].getEndRes() >= res))
486       {
487         currentSequenceGroup = allGroups[i];
488
489         return true;
490       }
491     }
492
493     return false;
494   }
495
496   /**
497    * DOCUMENT ME!
498    * 
499    * @param seq
500    *          DOCUMENT ME!
501    * @param start
502    *          DOCUMENT ME!
503    * @param end
504    *          DOCUMENT ME!
505    * @param x1
506    *          DOCUMENT ME!
507    * @param y1
508    *          DOCUMENT ME!
509    * @param width
510    *          DOCUMENT ME!
511    * @param height
512    *          DOCUMENT ME!
513    */
514   public void drawHighlightedText(SequenceI seq, int start, int end,
515           int x1, int y1)
516   {
517     int pady = av.getCharHeight() / 5;
518     int charOffset = 0;
519     graphics.setColor(Color.BLACK);
520     graphics.fillRect(x1, y1, av.getCharWidth() * (end - start + 1),
521             av.getCharHeight());
522     graphics.setColor(Color.white);
523
524     char s = '~';
525
526     // Need to find the sequence position here.
527     if (av.isValidCharWidth())
528     {
529       for (int i = start; i <= end; i++)
530       {
531         if (i < seq.getLength())
532         {
533           s = seq.getCharAt(i);
534         }
535
536         charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
537         graphics.drawString(String.valueOf(s),
538                 charOffset + x1 + (av.getCharWidth() * (i - start)),
539                 (y1 + av.getCharHeight()) - pady);
540       }
541     }
542   }
543
544   public void drawCursor(SequenceI seq, int res, int x1, int y1)
545   {
546     int pady = av.getCharHeight() / 5;
547     int charOffset = 0;
548     graphics.setColor(Color.black);
549     graphics.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight());
550
551     if (av.isValidCharWidth())
552     {
553       graphics.setColor(Color.white);
554
555       char s = seq.getCharAt(res);
556
557       charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
558       graphics.drawString(String.valueOf(s), charOffset + x1,
559               (y1 + av.getCharHeight()) - pady);
560     }
561
562   }
563 }