JAL-1960 updated unit test to include the test case where a sequences has no PDBId...
[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 public class SequenceRenderer implements jalview.api.SequenceRenderer
33 {
34   final static int CHAR_TO_UPPER = 'A' - 'a';
35
36   AlignViewport av;
37
38   FontMetrics fm;
39
40   boolean renderGaps = true;
41
42   SequenceGroup currentSequenceGroup = null;
43
44   SequenceGroup[] allGroups = null;
45
46   Color resBoxColour;
47
48   Graphics graphics;
49
50   boolean monospacedFont;
51
52   boolean forOverview = false;
53
54   /**
55    * Creates a new SequenceRenderer object.
56    * 
57    * @param av
58    *          DOCUMENT ME!
59    */
60   public SequenceRenderer(AlignViewport av)
61   {
62     this.av = av;
63   }
64
65   /**
66    * DOCUMENT ME!
67    * 
68    * @param b
69    *          DOCUMENT ME!
70    */
71   public void prepare(Graphics g, boolean renderGaps)
72   {
73     graphics = g;
74     fm = g.getFontMetrics();
75
76     // If EPS graphics, stringWidth will be a double, not an int
77     double dwidth = fm.getStringBounds("M", g).getWidth();
78
79     monospacedFont = (dwidth == fm.getStringBounds("|", g).getWidth() && av
80             .getCharWidth() == dwidth);
81
82     this.renderGaps = renderGaps;
83   }
84
85   @Override
86   public Color getResidueBoxColour(SequenceI seq, int i)
87   {
88     allGroups = av.getAlignment().findAllGroups(seq);
89
90     if (inCurrentSequenceGroup(i))
91     {
92       if (currentSequenceGroup.getDisplayBoxes())
93       {
94         getBoxColour(currentSequenceGroup.cs, seq, i);
95       }
96     }
97     else if (av.getShowBoxes())
98     {
99       getBoxColour(av.getGlobalColourScheme(), seq, i);
100     }
101
102     return resBoxColour;
103   }
104
105   /**
106    * Get the residue colour at the given sequence position - as determined by
107    * the sequence group colour (if any), else the colour scheme, possibly
108    * overridden by a feature colour.
109    * 
110    * @param seq
111    * @param position
112    * @param fr
113    * @return
114    */
115   @Override
116   public Color getResidueColour(final SequenceI seq, int position,
117           FeatureRenderer fr)
118   {
119     // TODO replace 8 or so code duplications with calls to this method
120     // (refactored as needed)
121     Color col = getResidueBoxColour(seq, position);
122
123     if (fr != null)
124     {
125       col = fr.findFeatureColour(col, seq, position);
126     }
127     return col;
128   }
129
130   /**
131    * DOCUMENT ME!
132    * 
133    * @param cs
134    *          DOCUMENT ME!
135    * @param seq
136    *          DOCUMENT ME!
137    * @param i
138    *          DOCUMENT ME!
139    */
140   void getBoxColour(ColourSchemeI cs, SequenceI seq, int i)
141   {
142     if (cs != null)
143     {
144       resBoxColour = cs.findColour(seq.getCharAt(i), i, seq);
145     }
146     else if (forOverview
147             && !jalview.util.Comparison.isGap(seq.getCharAt(i)))
148     {
149       resBoxColour = Color.lightGray;
150     }
151     else
152     {
153       resBoxColour = Color.white;
154     }
155   }
156
157   /**
158    * DOCUMENT ME!
159    * 
160    * @param g
161    *          DOCUMENT ME!
162    * @param seq
163    *          DOCUMENT ME!
164    * @param sg
165    *          DOCUMENT ME!
166    * @param start
167    *          DOCUMENT ME!
168    * @param end
169    *          DOCUMENT ME!
170    * @param x1
171    *          DOCUMENT ME!
172    * @param y1
173    *          DOCUMENT ME!
174    * @param width
175    *          DOCUMENT ME!
176    * @param height
177    *          DOCUMENT ME!
178    */
179   public void drawSequence(SequenceI seq, SequenceGroup[] sg, int start,
180           int end, int y1)
181   {
182     allGroups = sg;
183
184     drawBoxes(seq, start, end, y1);
185
186     if (av.validCharWidth)
187     {
188       drawText(seq, start, end, y1);
189     }
190   }
191
192   /**
193    * DOCUMENT ME!
194    * 
195    * @param seq
196    *          DOCUMENT ME!
197    * @param start
198    *          DOCUMENT ME!
199    * @param end
200    *          DOCUMENT ME!
201    * @param x1
202    *          DOCUMENT ME!
203    * @param y1
204    *          DOCUMENT ME!
205    * @param width
206    *          DOCUMENT ME!
207    * @param height
208    *          DOCUMENT ME!
209    */
210   public synchronized void drawBoxes(SequenceI seq, int start, int end,
211           int y1)
212   {
213     if (seq == null)
214     {
215       return; // fix for racecondition
216     }
217     int i = start;
218     int length = seq.getLength();
219
220     int curStart = -1;
221     int curWidth = av.getCharWidth(), avWidth = av.getCharWidth(), avHeight = av
222             .getCharHeight();
223
224     Color tempColour = null;
225
226     while (i <= end)
227     {
228       resBoxColour = Color.white;
229
230       if (i < length)
231       {
232         if (inCurrentSequenceGroup(i))
233         {
234           if (currentSequenceGroup.getDisplayBoxes())
235           {
236             getBoxColour(currentSequenceGroup.cs, seq, i);
237           }
238         }
239         else if (av.getShowBoxes())
240         {
241           getBoxColour(av.getGlobalColourScheme(), seq, i);
242         }
243
244       }
245
246       if (resBoxColour != tempColour)
247       {
248         if (tempColour != null)
249         {
250           graphics.fillRect(avWidth * (curStart - start), y1, curWidth,
251                   avHeight);
252         }
253
254         graphics.setColor(resBoxColour);
255
256         curStart = i;
257         curWidth = avWidth;
258         tempColour = resBoxColour;
259       }
260       else
261       {
262         curWidth += avWidth;
263       }
264
265       i++;
266     }
267
268     graphics.fillRect(avWidth * (curStart - start), y1, curWidth, avHeight);
269
270   }
271
272   /**
273    * DOCUMENT ME!
274    * 
275    * @param seq
276    *          DOCUMENT ME!
277    * @param start
278    *          DOCUMENT ME!
279    * @param end
280    *          DOCUMENT ME!
281    * @param x1
282    *          DOCUMENT ME!
283    * @param y1
284    *          DOCUMENT ME!
285    * @param width
286    *          DOCUMENT ME!
287    * @param height
288    *          DOCUMENT ME!
289    */
290   public void drawText(SequenceI seq, int start, int end, int y1)
291   {
292     y1 += av.getCharHeight() - av.getCharHeight() / 5; // height/5 replaces pady
293     int charOffset = 0;
294     char s;
295
296     if (end + 1 >= seq.getLength())
297     {
298       end = seq.getLength() - 1;
299     }
300     graphics.setColor(av.getTextColour());
301
302     if (monospacedFont && av.getShowText() && allGroups.length == 0
303             && !av.getColourText() && av.getThresholdTextColour() == 0)
304     {
305       if (av.isRenderGaps())
306       {
307         graphics.drawString(seq.getSequenceAsString(start, end + 1), 0, y1);
308       }
309       else
310       {
311         char gap = av.getGapCharacter();
312         graphics.drawString(seq.getSequenceAsString(start, end + 1)
313                 .replace(gap, ' '), 0, y1);
314       }
315     }
316     else
317     {
318       boolean srep = av.isDisplayReferenceSeq();
319       boolean getboxColour = false;
320       boolean isarep = av.getAlignment().getSeqrep() == seq;
321       boolean isgrep = currentSequenceGroup != null ? currentSequenceGroup
322               .getSeqrep() == seq : false;
323       char sr_c;
324       for (int i = start; i <= end; i++)
325       {
326
327         graphics.setColor(av.getTextColour());
328         getboxColour = false;
329         s = seq.getCharAt(i);
330
331         if (!renderGaps && jalview.util.Comparison.isGap(s))
332         {
333           continue;
334         }
335
336         if (inCurrentSequenceGroup(i))
337         {
338           if (!currentSequenceGroup.getDisplayText())
339           {
340             continue;
341           }
342
343           if (currentSequenceGroup.thresholdTextColour > 0
344                   || currentSequenceGroup.getColourText())
345           {
346             getboxColour = true;
347             getBoxColour(currentSequenceGroup.cs, seq, 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.getGlobalColourScheme(), 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.getGlobalColourScheme(), 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 }