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