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