Class |
Line # |
Actions |
|||
---|---|---|---|---|---|
SequenceRenderer | 36 | 121 | 62 |
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 | 879 | public SequenceRenderer(AlignViewportI viewport) |
62 | { | |
63 | 879 | this.av = viewport; |
64 | 879 | resColourFinder = new ResidueColourFinder(); |
65 | } | |
66 | ||
67 | /** | |
68 | * DOCUMENT ME! | |
69 | * | |
70 | * @param b | |
71 | * DOCUMENT ME! | |
72 | */ | |
73 | 2715 | @Override |
74 | public void prepare(Graphics g, boolean renderGaps) | |
75 | { | |
76 | 2715 | graphics = g; |
77 | 2715 | fm = g.getFontMetrics(); |
78 | ||
79 | // If EPS graphics, stringWidth will be a double, not an int | |
80 | 2715 | double dwidth = fm.getStringBounds("M", g).getWidth(); |
81 | ||
82 | 2715 | monospacedFont = (dwidth == fm.getStringBounds("|", g).getWidth() |
83 | && av.getCharWidth() == dwidth); | |
84 | ||
85 | 2715 | 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 | 37819 | @Override |
99 | public Color getResidueColour(final SequenceI seq, int position, | |
100 | FeatureColourFinder finder) | |
101 | { | |
102 | 37819 | allGroups = av.getAlignment().findAllGroups(seq); |
103 | 37819 | 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 | 12957 | public void drawSequence(SequenceI seq, SequenceGroup[] sg, int start, |
130 | int end, int y1) | |
131 | { | |
132 | 12957 | allGroups = sg; |
133 | ||
134 | 12957 | drawBoxes(seq, start, end, y1); |
135 | ||
136 | 12957 | if (av.isValidCharWidth()) |
137 | { | |
138 | 12957 | 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 | 12957 | public synchronized void drawBoxes(SequenceI seq, int start, int end, |
161 | int y1) | |
162 | { | |
163 | 12957 | Color resBoxColour = Color.white; |
164 | ||
165 | 12957 | if (seq == null) |
166 | { | |
167 | 0 | return; // fix for racecondition |
168 | } | |
169 | 12957 | int i = start; |
170 | 12957 | int length = seq.getLength(); |
171 | ||
172 | 12957 | int curStart = -1; |
173 | 12957 | int curWidth = av.getCharWidth(), avWidth = av.getCharWidth(), |
174 | avHeight = av.getCharHeight(); | |
175 | ||
176 | 12957 | Color tempColour = null; |
177 | ||
178 | 737449 | while (i <= end) |
179 | { | |
180 | 724492 | resBoxColour = Color.white; |
181 | ||
182 | 724492 | if (i < length) |
183 | { | |
184 | 723779 | SequenceGroup currentSequenceGroup = resColourFinder |
185 | .getCurrentSequenceGroup(allGroups, i); | |
186 | 723779 | if (currentSequenceGroup != null) |
187 | { | |
188 | 291041 | if (currentSequenceGroup.getDisplayBoxes()) |
189 | { | |
190 | 291041 | resBoxColour = resColourFinder.getBoxColour( |
191 | currentSequenceGroup.getGroupColourScheme(), seq, i); | |
192 | } | |
193 | } | |
194 | 432738 | else if (av.getShowBoxes()) |
195 | { | |
196 | 432738 | resBoxColour = resColourFinder |
197 | .getBoxColour(av.getResidueShading(), seq, i); | |
198 | } | |
199 | } | |
200 | ||
201 | 724492 | if (resBoxColour != tempColour) |
202 | { | |
203 | 257665 | if (tempColour != null) |
204 | { | |
205 | 244708 | graphics.fillRect(avWidth * (curStart - start), y1, curWidth, |
206 | avHeight); | |
207 | } | |
208 | ||
209 | 257665 | graphics.setColor(resBoxColour); |
210 | ||
211 | 257665 | curStart = i; |
212 | 257665 | curWidth = avWidth; |
213 | 257665 | tempColour = resBoxColour; |
214 | } | |
215 | else | |
216 | { | |
217 | 466827 | curWidth += avWidth; |
218 | } | |
219 | ||
220 | 724492 | i++; |
221 | } | |
222 | ||
223 | 12957 | 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 | 12957 | public void drawText(SequenceI seq, int start, int end, int y1) |
246 | { | |
247 | 12957 | y1 += av.getCharHeight() - av.getCharHeight() / 5; // height/5 replaces pady |
248 | 12957 | int charOffset = 0; |
249 | 12957 | char s; |
250 | ||
251 | 12957 | if (end + 1 >= seq.getLength()) |
252 | { | |
253 | 1476 | end = seq.getLength() - 1; |
254 | } | |
255 | 12957 | graphics.setColor(av.getTextColour()); |
256 | ||
257 | 12957 | 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 | 12957 | if (graphics instanceof EpsGraphics2D |
266 | || graphics instanceof SVGGraphics2D) | |
267 | { | |
268 | 210 | drawAllText = false; |
269 | } | |
270 | 12957 | if (drawAllText) |
271 | { | |
272 | 0 | if (av.isRenderGaps()) |
273 | { | |
274 | 0 | graphics.drawString(seq.getSequenceAsString(start, end + 1), 0, y1); |
275 | } | |
276 | else | |
277 | { | |
278 | 0 | char gap = av.getGapCharacter(); |
279 | 0 | graphics.drawString( |
280 | seq.getSequenceAsString(start, end + 1).replace(gap, ' '), | |
281 | 0, y1); | |
282 | } | |
283 | } | |
284 | else | |
285 | { | |
286 | 12957 | boolean srep = av.isDisplayReferenceSeq(); |
287 | 12957 | boolean getboxColour = false; |
288 | 12957 | boolean isarep = av.getAlignment().getSeqrep() == seq; |
289 | 12957 | Color resBoxColour = Color.white; |
290 | ||
291 | 736736 | for (int i = start; i <= end; i++) |
292 | { | |
293 | ||
294 | 723779 | graphics.setColor(av.getTextColour()); |
295 | 723779 | getboxColour = false; |
296 | 723779 | s = seq.getCharAt(i); |
297 | ||
298 | 723779 | if (!renderGaps && jalview.util.Comparison.isGap(s)) |
299 | { | |
300 | 0 | continue; |
301 | } | |
302 | ||
303 | 723779 | SequenceGroup currentSequenceGroup = resColourFinder |
304 | .getCurrentSequenceGroup(allGroups, i); | |
305 | 723779 | if (currentSequenceGroup != null) |
306 | { | |
307 | 291041 | if (!currentSequenceGroup.getDisplayText()) |
308 | { | |
309 | 0 | continue; |
310 | } | |
311 | ||
312 | 291041 | if (currentSequenceGroup.thresholdTextColour > 0 |
313 | || currentSequenceGroup.getColourText()) | |
314 | { | |
315 | 0 | getboxColour = true; |
316 | 0 | resBoxColour = resColourFinder.getBoxColour( |
317 | currentSequenceGroup.getGroupColourScheme(), seq, i); | |
318 | ||
319 | 0 | if (currentSequenceGroup.getColourText()) |
320 | { | |
321 | 0 | graphics.setColor(resBoxColour.darker()); |
322 | } | |
323 | ||
324 | 0 | if (currentSequenceGroup.thresholdTextColour > 0) |
325 | { | |
326 | 0 | if (resBoxColour.getRed() + resBoxColour.getBlue() |
327 | + resBoxColour | |
328 | .getGreen() < currentSequenceGroup.thresholdTextColour) | |
329 | { | |
330 | 0 | graphics.setColor(currentSequenceGroup.textColour2); |
331 | } | |
332 | } | |
333 | } | |
334 | else | |
335 | { | |
336 | 291041 | graphics.setColor(currentSequenceGroup.textColour); |
337 | } | |
338 | 291041 | boolean isgrep = currentSequenceGroup != null |
339 | ? currentSequenceGroup.getSeqrep() == seq | |
340 | : false; | |
341 | 291041 | if (!isarep && !isgrep |
342 | && currentSequenceGroup.getShowNonconserved()) // todo | |
343 | // optimize | |
344 | { | |
345 | // todo - use sequence group consensus | |
346 | 0 | s = getDisplayChar(srep, i, s, '.', currentSequenceGroup); |
347 | ||
348 | } | |
349 | ||
350 | } | |
351 | else | |
352 | { | |
353 | 432738 | if (!av.getShowText()) |
354 | { | |
355 | 0 | continue; |
356 | } | |
357 | ||
358 | 432738 | if (av.getColourText()) |
359 | { | |
360 | 0 | getboxColour = true; |
361 | 0 | resBoxColour = resColourFinder |
362 | .getBoxColour(av.getResidueShading(), seq, i); | |
363 | ||
364 | 0 | if (av.getShowBoxes()) |
365 | { | |
366 | 0 | graphics.setColor(resBoxColour.darker()); |
367 | } | |
368 | else | |
369 | { | |
370 | 0 | graphics.setColor(resBoxColour); |
371 | } | |
372 | } | |
373 | ||
374 | 432738 | if (av.getThresholdTextColour() > 0) |
375 | { | |
376 | 0 | if (!getboxColour) |
377 | { | |
378 | 0 | resBoxColour = resColourFinder |
379 | .getBoxColour(av.getResidueShading(), seq, i); | |
380 | } | |
381 | ||
382 | 0 | if (resBoxColour.getRed() + resBoxColour.getBlue() |
383 | + resBoxColour.getGreen() < av.getThresholdTextColour()) | |
384 | { | |
385 | 0 | graphics.setColor(av.getTextColour2()); |
386 | } | |
387 | } | |
388 | 432738 | if (!isarep && av.getShowUnconserved()) |
389 | { | |
390 | 0 | s = getDisplayChar(srep, i, s, '.', null); |
391 | ||
392 | } | |
393 | ||
394 | } | |
395 | ||
396 | 723779 | charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2; |
397 | 723779 | 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 | 0 | 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 | 0 | char conschar = (usesrep) ? (currentGroup == null |
421 | || position < currentGroup.getStartRes() | |
422 | || position > currentGroup.getEndRes() | |
423 | ? av.getAlignment().getSeqrep().getCharAt(position) | |
424 | 0 | : (currentGroup.getSeqrep() != null |
425 | ? currentGroup.getSeqrep().getCharAt(position) | |
426 | : av.getAlignment().getSeqrep() | |
427 | .getCharAt(position))) | |
428 | 0 | : (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 | 0 | if (!jalview.util.Comparison.isGap(conschar) |
439 | && (sequenceChar == conschar | |
440 | || sequenceChar + CHAR_TO_UPPER == conschar)) | |
441 | { | |
442 | 0 | sequenceChar = conservedChar; |
443 | } | |
444 | 0 | 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 | 12 | public void drawHighlightedText(SequenceI seq, int start, int end, int x1, |
466 | int y1) | |
467 | { | |
468 | 12 | int pady = av.getCharHeight() / 5; |
469 | 12 | int charOffset = 0; |
470 | 12 | graphics.setColor(Color.BLACK); |
471 | 12 | graphics.fillRect(x1, y1, av.getCharWidth() * (end - start + 1), |
472 | av.getCharHeight()); | |
473 | 12 | graphics.setColor(Color.white); |
474 | ||
475 | 12 | char s = '~'; |
476 | ||
477 | // Need to find the sequence position here. | |
478 | 12 | if (av.isValidCharWidth()) |
479 | { | |
480 | 48 | for (int i = start; i <= end; i++) |
481 | { | |
482 | 36 | if (i < seq.getLength()) |
483 | { | |
484 | 36 | s = seq.getCharAt(i); |
485 | } | |
486 | ||
487 | 36 | charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2; |
488 | 36 | 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 | 0 | public void drawCursor(Graphics g, char s, int x1, int y1) |
508 | { | |
509 | 0 | int pady = av.getCharHeight() / 5; |
510 | 0 | int charOffset = 0; |
511 | 0 | g.setColor(Color.black); |
512 | 0 | g.fillRect(x1, y1, av.getCharWidth(), av.getCharHeight()); |
513 | ||
514 | 0 | if (av.isValidCharWidth()) |
515 | { | |
516 | 0 | g.setColor(Color.white); |
517 | 0 | charOffset = (av.getCharWidth() - fm.charWidth(s)) / 2; |
518 | 0 | g.drawString(String.valueOf(s), charOffset + x1, |
519 | (y1 + av.getCharHeight()) - pady); | |
520 | } | |
521 | ||
522 | } | |
523 | } |