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