33d34a276f46b539fd144c193713f96c30d1bc3c
[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.bin.Cache;
25 import jalview.datamodel.SequenceGroup;
26 import jalview.datamodel.SequenceI;
27 import jalview.renderer.ResidueColourFinder;
28 import jalview.renderer.seqfeatures.FeatureColourFinder;
29
30 import java.awt.Color;
31 import java.awt.FontMetrics;
32 import java.awt.Graphics;
33
34 import org.jfree.graphics2d.svg.SVGGraphics2D;
35 import org.jibble.epsgraphics.EpsGraphics2D;
36
37 public class SequenceRenderer implements jalview.api.SequenceRenderer
38 {
39   final static int CHAR_TO_UPPER = 'A' - 'a';
40
41   AlignViewportI av;
42
43   FontMetrics fm;
44
45   boolean renderGaps = true;
46
47   SequenceGroup[] allGroups = null;
48
49   // Color resBoxColour;
50
51   Graphics graphics;
52
53   boolean monospacedFont;
54
55   ResidueColourFinder resColourFinder;
56
57   private boolean colourGaps;
58   private Color gapColour=Color.white;
59
60   /**
61    * Creates a new SequenceRenderer object
62    * 
63    * @param viewport
64    */
65   public SequenceRenderer(AlignViewportI viewport)
66   {
67     this.av = viewport;
68     resColourFinder = new ResidueColourFinder();
69   }
70
71   /**
72    * initialise state for a render
73    * @param renderGaps - whether gap symbols are shown
74    * @param colourGaps - whether boxes for gaps are to be coloured
75    */
76   public void prepare(Graphics g, boolean renderGaps, boolean colourGaps)
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()
85             && av.getCharWidth() == dwidth);
86
87     this.renderGaps = renderGaps;
88     this.colourGaps = colourGaps;
89     this.gapColour = Color.white;
90     if (colourGaps)
91     {
92       this.gapColour = Cache.getDefaultColour(Preferences.GAP_COLOUR,
93               jalview.renderer.OverviewResColourFinder.OVERVIEW_DEFAULT_GAP);
94     }
95     resColourFinder = new ResidueColourFinder(gapColour);
96   }
97
98   /**
99    * Get the residue colour at the given sequence position - as determined by
100    * the sequence group colour (if any), else the colour scheme, possibly
101    * overridden by a feature colour.
102    * 
103    * @param seq
104    * @param position
105    * @param finder
106    * @return
107    */
108   @Override
109   public Color getResidueColour(final SequenceI seq, int position,
110           FeatureColourFinder finder)
111   {
112     allGroups = av.getAlignment().findAllGroups(seq);
113     return resColourFinder.getResidueColour(av.getShowBoxes(),
114             av.getResidueShading(),
115             allGroups, seq, position,
116             finder);
117   }
118
119   /**
120    * DOCUMENT ME!
121    * 
122    * @param g
123    *          DOCUMENT ME!
124    * @param seq
125    *          DOCUMENT ME!
126    * @param sg
127    *          DOCUMENT ME!
128    * @param start
129    *          DOCUMENT ME!
130    * @param end
131    *          DOCUMENT ME!
132    * @param x1
133    *          DOCUMENT ME!
134    * @param y1
135    *          DOCUMENT ME!
136    * @param width
137    *          DOCUMENT ME!
138    * @param height
139    *          DOCUMENT ME!
140    */
141   public void drawSequence(SequenceI seq, SequenceGroup[] sg, int start,
142           int end, int y1)
143   {
144     allGroups = sg;
145
146     drawBoxes(seq, start, end, y1);
147
148     if (av.isValidCharWidth())
149     {
150       drawText(seq, start, end, y1);
151     }
152   }
153
154   /**
155    * DOCUMENT ME!
156    * 
157    * @param seq
158    *          DOCUMENT ME!
159    * @param start
160    *          DOCUMENT ME!
161    * @param end
162    *          DOCUMENT ME!
163    * @param x1
164    *          DOCUMENT ME!
165    * @param y1
166    *          DOCUMENT ME!
167    * @param width
168    *          DOCUMENT ME!
169    * @param height
170    *          DOCUMENT ME!
171    */
172   public synchronized void drawBoxes(SequenceI seq, int start, int end,
173           int y1)
174   {
175     Color resBoxColour = gapColour;
176
177     if (seq == null)
178     {
179       return; // fix for racecondition
180     }
181     int i = start;
182     int length = seq.getLength();
183
184     int curStart = -1;
185     int curWidth = av.getCharWidth(), avWidth = av.getCharWidth(),
186             avHeight = av.getCharHeight();
187
188     Color tempColour = null;
189
190     while (i <= end)
191     {
192       resBoxColour = Color.white;
193
194       if (i < length)
195       {
196         SequenceGroup currentSequenceGroup = resColourFinder
197                 .getCurrentSequenceGroup(
198                 allGroups, i);
199         if (currentSequenceGroup != null)
200         {
201           if (currentSequenceGroup.getDisplayBoxes())
202           {
203             resBoxColour = resColourFinder.getBoxColour(
204                     currentSequenceGroup.getGroupColourScheme(), seq,
205                     i);
206           }
207         }
208         else if (av.getShowBoxes())
209         {
210           resBoxColour = resColourFinder
211                   .getBoxColour(av.getResidueShading(), seq, i);
212         }
213       } else {
214         resBoxColour = gapColour;
215       }
216
217       if (resBoxColour != tempColour)
218       {
219         if (tempColour != null)
220         {
221           graphics.fillRect(avWidth * (curStart - start), y1, curWidth,
222                   avHeight);
223         }
224
225         graphics.setColor(resBoxColour);
226
227         curStart = i;
228         curWidth = avWidth;
229         tempColour = resBoxColour;
230       }
231       else
232       {
233         curWidth += avWidth;
234       }
235
236       i++;
237     }
238
239     graphics.fillRect(avWidth * (curStart - start), y1, curWidth, avHeight);
240
241   }
242
243   /**
244    * DOCUMENT ME!
245    * 
246    * @param seq
247    *          DOCUMENT ME!
248    * @param start
249    *          DOCUMENT ME!
250    * @param end
251    *          DOCUMENT ME!
252    * @param x1
253    *          DOCUMENT ME!
254    * @param y1
255    *          DOCUMENT ME!
256    * @param width
257    *          DOCUMENT ME!
258    * @param height
259    *          DOCUMENT ME!
260    */
261   public void drawText(SequenceI seq, int start, int end, int y1)
262   {
263     y1 += av.getCharHeight() - av.getCharHeight() / 5; // height/5 replaces pady
264     int charOffset = 0;
265     char s;
266
267     if (end + 1 >= seq.getLength())
268     {
269       end = seq.getLength() - 1;
270     }
271     graphics.setColor(av.getTextColour());
272
273     boolean drawAllText = monospacedFont && av.getShowText() && allGroups.length == 0
274             && !av.getColourText() && av.getThresholdTextColour() == 0;
275
276     /*
277      * EPS or SVG misaligns monospaced strings (JAL-3239)
278      * so always draw these one character at a time
279      */
280     if (graphics instanceof EpsGraphics2D
281             || graphics instanceof SVGGraphics2D)
282     {
283       drawAllText = false;
284     }
285     if (drawAllText)
286     {
287       if (av.isRenderGaps())
288       {
289         graphics.drawString(seq.getSequenceAsString(start, end + 1), 0, y1);
290       }
291       else
292       {
293         char gap = av.getGapCharacter();
294         graphics.drawString(
295                 seq.getSequenceAsString(start, end + 1).replace(gap, ' '),
296                 0, y1);
297       }
298     }
299     else
300     {
301       boolean srep = av.isDisplayReferenceSeq();
302       boolean getboxColour = false;
303       boolean isarep = av.getAlignment().getSeqrep() == seq;
304       Color resBoxColour = Color.white;
305
306       for (int i = start; i <= end; i++)
307       {
308
309         graphics.setColor(av.getTextColour());
310         getboxColour = false;
311         s = seq.getCharAt(i);
312
313         if (!renderGaps && jalview.util.Comparison.isGap(s))
314         {
315           continue;
316         }
317
318         SequenceGroup currentSequenceGroup = resColourFinder
319                 .getCurrentSequenceGroup(
320                 allGroups, i);
321         if (currentSequenceGroup != null)
322         {
323           if (!currentSequenceGroup.getDisplayText())
324           {
325             continue;
326           }
327
328           if (currentSequenceGroup.thresholdTextColour > 0
329                   || currentSequenceGroup.getColourText())
330           {
331             getboxColour = true;
332             resBoxColour = resColourFinder.getBoxColour(
333                     currentSequenceGroup.getGroupColourScheme(), seq,
334                     i);
335
336             if (currentSequenceGroup.getColourText())
337             {
338               graphics.setColor(resBoxColour.darker());
339             }
340
341             if (currentSequenceGroup.thresholdTextColour > 0)
342             {
343               if (resBoxColour.getRed() + resBoxColour.getBlue()
344                       + resBoxColour
345                               .getGreen() < currentSequenceGroup.thresholdTextColour)
346               {
347                 graphics.setColor(currentSequenceGroup.textColour2);
348               }
349             }
350           }
351           else
352           {
353             graphics.setColor(currentSequenceGroup.textColour);
354           }
355           boolean isgrep = currentSequenceGroup != null
356                   ? currentSequenceGroup.getSeqrep() == seq : false;
357           if (!isarep && !isgrep
358                   && currentSequenceGroup.getShowNonconserved()) // todo
359                                                                  // optimize
360           {
361             // todo - use sequence group consensus
362             s = getDisplayChar(srep, i, s, '.', currentSequenceGroup);
363
364           }
365
366         }
367         else
368         {
369           if (!av.getShowText())
370           {
371             continue;
372           }
373
374           if (av.getColourText())
375           {
376             getboxColour = true;
377             resBoxColour = resColourFinder
378                     .getBoxColour(av.getResidueShading(), seq, i);
379
380             if (av.getShowBoxes())
381             {
382               graphics.setColor(resBoxColour.darker());
383             }
384             else
385             {
386               graphics.setColor(resBoxColour);
387             }
388           }
389
390           if (av.getThresholdTextColour() > 0)
391           {
392             if (!getboxColour)
393             {
394               resBoxColour = resColourFinder
395                       .getBoxColour(av.getResidueShading(), seq, i);
396             }
397
398             if (resBoxColour.getRed() + resBoxColour.getBlue()
399                     + resBoxColour.getGreen() < av.getThresholdTextColour())
400             {
401               graphics.setColor(av.getTextColour2());
402             }
403           }
404           if (!isarep && av.getShowUnconserved())
405           {
406             s = getDisplayChar(srep, i, s, '.', null);
407
408           }
409
410         }
411
412         charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
413         graphics.drawString(String.valueOf(s),
414                 charOffset + av.getCharWidth() * (i - start), y1);
415
416       }
417     }
418   }
419
420   /**
421    * Returns 'conservedChar' to represent the given position if the sequence
422    * character at that position is equal to the consensus (ignoring case), else
423    * returns the sequence character
424    * 
425    * @param usesrep
426    * @param position
427    * @param sequenceChar
428    * @param conservedChar
429    * @return
430    */
431   private char getDisplayChar(final boolean usesrep, int position,
432           char sequenceChar, char conservedChar, SequenceGroup currentGroup)
433   {
434     // TODO - use currentSequenceGroup rather than alignment
435     // currentSequenceGroup.getConsensus()
436     char conschar = (usesrep) ? (currentGroup == null
437             || position < currentGroup.getStartRes()
438             || position > currentGroup.getEndRes()
439                     ? av.getAlignment().getSeqrep().getCharAt(position)
440                     : (currentGroup.getSeqrep() != null
441                             ? currentGroup.getSeqrep().getCharAt(position)
442                             : av.getAlignment().getSeqrep()
443                                     .getCharAt(position)))
444             : (currentGroup != null && currentGroup.getConsensus() != null
445                     && position >= currentGroup.getStartRes()
446                     && position <= currentGroup.getEndRes()
447                     && currentGroup
448                             .getConsensus().annotations.length > position)
449                                     ? currentGroup
450                                             .getConsensus().annotations[position].displayCharacter
451                                                     .charAt(0)
452                                     : av.getAlignmentConsensusAnnotation().annotations[position].displayCharacter
453                                             .charAt(0);
454     if (!jalview.util.Comparison.isGap(conschar)
455             && (sequenceChar == conschar
456                     || sequenceChar + CHAR_TO_UPPER == conschar))
457     {
458       sequenceChar = conservedChar;
459     }
460     return sequenceChar;
461   }
462
463   /**
464    * DOCUMENT ME!
465    * 
466    * @param seq
467    *          DOCUMENT ME!
468    * @param start
469    *          DOCUMENT ME!
470    * @param end
471    *          DOCUMENT ME!
472    * @param x1
473    *          DOCUMENT ME!
474    * @param y1
475    *          DOCUMENT ME!
476    * @param width
477    *          DOCUMENT ME!
478    * @param height
479    *          DOCUMENT ME!
480    */
481   public void drawHighlightedText(SequenceI seq, int start, int end, int x1,
482           int y1)
483   {
484     int pady = av.getCharHeight() / 5;
485     int charOffset = 0;
486     graphics.setColor(Color.BLACK);
487     graphics.fillRect(x1, y1, av.getCharWidth() * (end - start + 1),
488             av.getCharHeight());
489     graphics.setColor(Color.white);
490
491     char s = '~';
492
493     // Need to find the sequence position here.
494     if (av.isValidCharWidth())
495     {
496       for (int i = start; i <= end; i++)
497       {
498         if (i < seq.getLength())
499         {
500           s = seq.getCharAt(i);
501         }
502
503         charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
504         graphics.drawString(String.valueOf(s),
505                 charOffset + x1 + (av.getCharWidth() * (i - start)),
506                 (y1 + av.getCharHeight()) - pady);
507       }
508     }
509   }
510
511   /**
512    * Draw a sequence canvas cursor
513    * 
514    * @param g
515    *          graphics context to draw on
516    * @param s
517    *          character to draw at cursor
518    * @param x1
519    *          x position of cursor in graphics context
520    * @param y1
521    *          y position of cursor in graphics context
522    */
523   public void drawCursor(Graphics g, char s, int x1, int y1)
524   {
525     int pady = av.getCharHeight() / 5;
526     int charOffset = 0;
527     g.setColor(Color.black);
528     g.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight());
529
530     if (av.isValidCharWidth())
531     {
532       g.setColor(Color.white);
533       charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2;
534       g.drawString(String.valueOf(s), charOffset + x1,
535               (y1 + av.getCharHeight()) - pady);
536     }
537
538   }
539 }