Add end of consensus test for wrapped edit
[jalview.git] / src / jalview / gui / SeqCanvas.java
1 /*\r
2  * Jalview - A Sequence Alignment Editor and Viewer\r
3  * Copyright (C) 2005 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle\r
4  *\r
5  * This program is free software; you can redistribute it and/or\r
6  * modify it under the terms of the GNU General Public License\r
7  * as published by the Free Software Foundation; either version 2\r
8  * of the License, or (at your option) any later version.\r
9  *\r
10  * This program is distributed in the hope that it will be useful,\r
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13  * GNU General Public License for more details.\r
14  *\r
15  * You should have received a copy of the GNU General Public License\r
16  * along with this program; if not, write to the Free Software\r
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA\r
18  */\r
19 package jalview.gui;\r
20 \r
21 import jalview.datamodel.*;\r
22 \r
23 import java.awt.*;\r
24 import java.awt.image.*;\r
25 \r
26 import javax.swing.*;\r
27 \r
28 \r
29 /**\r
30  * DOCUMENT ME!\r
31  *\r
32  * @author $author$\r
33  * @version $Revision$\r
34  */\r
35 public class SeqCanvas extends JComponent\r
36 {\r
37     FeatureRenderer fr;\r
38     SequenceRenderer sr;\r
39     BufferedImage img;\r
40     Graphics2D gg;\r
41     int imgWidth;\r
42     int imgHeight;\r
43     AlignViewport av;\r
44     boolean displaySearch = false;\r
45     int[] searchResults = null;\r
46     int chunkHeight;\r
47     int chunkWidth;\r
48     boolean fastPaint = false;\r
49     int LABEL_WEST;\r
50     int LABEL_EAST;\r
51 \r
52     boolean isOverview = false;\r
53 \r
54     /**\r
55      * Creates a new SeqCanvas object.\r
56      *\r
57      * @param av DOCUMENT ME!\r
58      */\r
59     public SeqCanvas(AlignViewport av)\r
60     {\r
61         this.av = av;\r
62         fr = new FeatureRenderer(av);\r
63         sr = new SequenceRenderer(av);\r
64         setLayout(new BorderLayout());\r
65         PaintRefresher.Register(this, av.alignment);\r
66         setBackground(Color.white);\r
67     }\r
68 \r
69     /**\r
70      * DOCUMENT ME!\r
71      *\r
72      * @param g DOCUMENT ME!\r
73      * @param startx DOCUMENT ME!\r
74      * @param endx DOCUMENT ME!\r
75      * @param ypos DOCUMENT ME!\r
76      */\r
77     void drawNorthScale(Graphics g, int startx, int endx, int ypos)\r
78     {\r
79         int scalestartx = startx - (startx % 10) + 10;\r
80 \r
81         g.setColor(Color.black);\r
82 \r
83         // NORTH SCALE\r
84         for (int i = scalestartx; i < endx; i += 10)\r
85         {\r
86             String string = String.valueOf(i);\r
87             g.drawString(string, (i - startx - 1) * av.charWidth,\r
88                 ypos - (av.charHeight / 2));\r
89 \r
90             g.drawLine(((i - startx - 1) * av.charWidth) + (av.charWidth / 2),\r
91                 (ypos + 2) - (av.charHeight / 2),\r
92                 ((i - startx - 1) * av.charWidth) + (av.charWidth / 2), ypos -\r
93                 2);\r
94         }\r
95     }\r
96 \r
97     /**\r
98      * DOCUMENT ME!\r
99      *\r
100      * @param g DOCUMENT ME!\r
101      * @param startx DOCUMENT ME!\r
102      * @param endx DOCUMENT ME!\r
103      * @param ypos DOCUMENT ME!\r
104      */\r
105     void drawWestScale(Graphics g, int startx, int endx, int ypos)\r
106     {\r
107         FontMetrics fm = getFontMetrics(av.getFont());\r
108         ypos += av.charHeight;\r
109 \r
110         // EAST SCALE\r
111         for (int i = 0; i < av.alignment.getHeight(); i++)\r
112         {\r
113             SequenceI seq = av.alignment.getSequenceAt(i);\r
114             int index = startx;\r
115             int value = -1;\r
116 \r
117             while (index < endx)\r
118             {\r
119                 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))\r
120                 {\r
121                     index++;\r
122 \r
123                     continue;\r
124                 }\r
125 \r
126                 value = av.alignment.getSequenceAt(i).findPosition(index);\r
127 \r
128                 break;\r
129             }\r
130 \r
131             if (value != -1)\r
132             {\r
133                 int x = LABEL_WEST - fm.stringWidth(String.valueOf(value))-av.charWidth/2;\r
134                 g.drawString(value + "", x,\r
135                     (ypos + (i * av.charHeight)) - (av.charHeight / 5));\r
136             }\r
137         }\r
138     }\r
139 \r
140     /**\r
141      * DOCUMENT ME!\r
142      *\r
143      * @param g DOCUMENT ME!\r
144      * @param startx DOCUMENT ME!\r
145      * @param endx DOCUMENT ME!\r
146      * @param ypos DOCUMENT ME!\r
147      */\r
148     void drawEastScale(Graphics g, int startx, int endx, int ypos)\r
149     {\r
150         ypos += av.charHeight;\r
151 \r
152         // EAST SCALE\r
153         for (int i = 0; i < av.alignment.getHeight(); i++)\r
154         {\r
155             SequenceI seq = av.alignment.getSequenceAt(i);\r
156             int index = endx;\r
157             int value = -1;\r
158 \r
159             while (index > startx)\r
160             {\r
161                 if (jalview.util.Comparison.isGap(seq.getCharAt(index)))\r
162                 {\r
163                     index--;\r
164 \r
165                     continue;\r
166                 }\r
167 \r
168                 value = av.alignment.getSequenceAt(i).findPosition(index);\r
169 \r
170                 break;\r
171             }\r
172 \r
173             if (value != -1)\r
174             {\r
175                 g.drawString(String.valueOf(value), av.charWidth/2,\r
176                     (ypos + (i * av.charHeight)) - (av.charHeight / 5));\r
177             }\r
178         }\r
179     }\r
180 \r
181     /**\r
182      * DOCUMENT ME!\r
183      *\r
184      * @param horizontal DOCUMENT ME!\r
185      * @param vertical DOCUMENT ME!\r
186      */\r
187     public void fastPaint(int horizontal, int vertical)\r
188     {\r
189         if (gg == null)\r
190         {\r
191             return;\r
192         }\r
193 \r
194         gg.copyArea(0, 0, imgWidth, imgHeight, -horizontal * av.charWidth,\r
195             -vertical * av.charHeight);\r
196 \r
197         int sr = av.startRes;\r
198         int er = av.endRes;\r
199         int ss = av.startSeq;\r
200         int es = av.endSeq;\r
201         int transX = 0;\r
202         int transY = 0;\r
203 \r
204         if (horizontal > 0) // scrollbar pulled right, image to the left\r
205         {\r
206             er ++;\r
207             transX = (er - sr - horizontal) * av.charWidth;\r
208             sr = er - horizontal;\r
209         }\r
210         else if (horizontal < 0)\r
211         {\r
212             er = sr - horizontal-1;\r
213         }\r
214         else if (vertical > 0) // scroll down\r
215         {\r
216             ss = es - vertical;\r
217 \r
218             if (ss < av.startSeq)\r
219             { // ie scrolling too fast, more than a page at a time\r
220                 ss = av.startSeq;\r
221             }\r
222             else\r
223             {\r
224                 transY = imgHeight - (vertical * av.charHeight);\r
225             }\r
226         }\r
227         else if (vertical < 0)\r
228         {\r
229             es = ss - vertical;\r
230 \r
231             if (es > av.endSeq)\r
232             {\r
233                 es = av.endSeq;\r
234             }\r
235         }\r
236 \r
237         gg.translate(transX, transY);\r
238         drawPanel(gg, sr, er, ss, es, sr, ss, 0);\r
239         gg.translate(-transX, -transY);\r
240 \r
241         fastPaint = true;\r
242         repaint();\r
243     }\r
244 \r
245     /**\r
246      * Definitions of startx and endx (hopefully):\r
247      * SMJS This is what I'm working towards!\r
248      *   startx is the first residue (starting at 0) to display.\r
249      *   endx   is the last residue to display (starting at 0).\r
250      *   starty is the first sequence to display (starting at 0).\r
251      *   endy   is the last sequence to display (starting at 0).\r
252      * NOTE 1: The av limits are set in setFont in this class and\r
253      * in the adjustment listener in SeqPanel when the scrollbars move.\r
254      */\r
255 \r
256     // Set this to false to force a full panel paint\r
257     public void paintComponent(Graphics g)\r
258     {\r
259         sr.renderGaps(av.renderGaps);\r
260 \r
261         if ((img != null) &&\r
262                 (fastPaint || (getWidth() != g.getClipBounds().width) ||\r
263                 (getHeight() != g.getClipBounds().height)))\r
264         {\r
265             g.drawImage(img, 0, 0, this);\r
266             fastPaint = false;\r
267 \r
268             return;\r
269         }\r
270 \r
271         // this draws the whole of the alignment\r
272         imgWidth = getWidth();\r
273         imgHeight = getHeight();\r
274 \r
275         imgWidth -= (imgWidth % av.charWidth);\r
276         imgHeight -= (imgHeight % av.charHeight);\r
277 \r
278         if ((imgWidth < 1) || (imgHeight < 1))\r
279         {\r
280             return;\r
281         }\r
282 \r
283 \r
284         img = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);\r
285         gg = (Graphics2D) img.getGraphics();\r
286         gg.setFont(av.getFont());\r
287         gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,\r
288           RenderingHints.VALUE_ANTIALIAS_ON);\r
289 \r
290         gg.setColor(Color.white);\r
291         gg.fillRect(0, 0, imgWidth, imgHeight);\r
292 \r
293         chunkWidth = getWrappedCanvasWidth(getWidth());\r
294         chunkHeight = (av.getAlignment().getHeight() + 2) * av.charHeight;\r
295 \r
296         av.setChunkHeight(chunkHeight);\r
297         av.setChunkWidth(chunkWidth);\r
298 \r
299         if (av.getWrapAlignment())\r
300         {\r
301             drawWrappedPanel(gg, getWidth(), getHeight(), av.startRes);\r
302         }\r
303         else\r
304         {\r
305             drawPanel(gg, av.startRes, av.endRes, av.startSeq, av.endSeq,\r
306                 av.startRes, av.startSeq, 0);\r
307         }\r
308 \r
309         g.drawImage(img, 0, 0, this);\r
310     }\r
311 \r
312     /**\r
313      * DOCUMENT ME!\r
314      *\r
315      * @param cwidth DOCUMENT ME!\r
316      *\r
317      * @return DOCUMENT ME!\r
318      */\r
319     public int getWrappedCanvasWidth(int cwidth)\r
320     {\r
321         FontMetrics fm = getFontMetrics(av.getFont());\r
322 \r
323         LABEL_EAST = 0;\r
324         LABEL_WEST = 0;\r
325 \r
326         if (av.scaleRightWrapped)\r
327         {\r
328             LABEL_EAST = fm.stringWidth(getMask()+"0");\r
329         }\r
330 \r
331         if (av.scaleLeftWrapped)\r
332         {\r
333             LABEL_WEST = fm.stringWidth(getMask());\r
334         }\r
335 \r
336         return (cwidth - LABEL_EAST - LABEL_WEST) / av.charWidth;\r
337     }\r
338 \r
339 \r
340     /**\r
341      * Generates a string of zeroes.\r
342      * @return String\r
343      */\r
344     String getMask()\r
345     {\r
346       String mask = "0";\r
347       for (int i = av.alignment.getWidth(); i > 0; i /= 10)\r
348       {\r
349         mask += "0";\r
350       }\r
351       return mask;\r
352     }\r
353 \r
354     /**\r
355      * DOCUMENT ME!\r
356      *\r
357      * @param g DOCUMENT ME!\r
358      * @param canvasWidth DOCUMENT ME!\r
359      * @param canvasHeight DOCUMENT ME!\r
360      * @param startRes DOCUMENT ME!\r
361      */\r
362     public void drawWrappedPanel(Graphics g, int canvasWidth, int canvasHeight,\r
363         int startRes)\r
364     {\r
365 \r
366         AlignmentI al = av.getAlignment();\r
367 \r
368         FontMetrics fm = getFontMetrics(av.getFont());\r
369 \r
370         int LABEL_EAST = 0;\r
371 \r
372         if (av.scaleRightWrapped)\r
373         {\r
374             LABEL_EAST = fm.stringWidth(getMask()+"0");\r
375         }\r
376 \r
377         int LABEL_WEST = 0;\r
378 \r
379         if (av.scaleLeftWrapped)\r
380         {\r
381             LABEL_WEST = fm.stringWidth(getMask());\r
382         }\r
383 \r
384         int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / av.charWidth;\r
385         int cHeight = (av.getAlignment().getHeight() + 2) * av.charHeight;\r
386 \r
387         av.endRes = av.startRes + cWidth;\r
388 \r
389         int endx = (startRes + cWidth) - 1;\r
390         int ypos = 2 * av.charHeight;\r
391 \r
392         while ((ypos <= canvasHeight) && (startRes < av.alignment.getWidth()))\r
393         {\r
394             g.setColor(Color.black);\r
395 \r
396             if (av.scaleLeftWrapped)\r
397             {\r
398                 drawWestScale(g, startRes, endx, ypos);\r
399             }\r
400 \r
401             if (av.scaleRightWrapped)\r
402             {\r
403                 g.translate(canvasWidth - LABEL_EAST, 0);\r
404                 drawEastScale(g, startRes, endx, ypos);\r
405                 g.translate(-(canvasWidth - LABEL_EAST), 0);\r
406             }\r
407 \r
408             g.translate(LABEL_WEST, 0);\r
409 \r
410             if (av.scaleAboveWrapped)\r
411             {\r
412                 drawNorthScale(g, startRes, endx, ypos);\r
413             }\r
414 \r
415             // When printing we have an extra clipped region,\r
416             // the Printable page which we need to account for here\r
417             Shape clip = g.getClip();\r
418 \r
419             if (clip == null)\r
420             {\r
421                 g.setClip(0, 0, cWidth * av.charWidth, canvasHeight);\r
422             }\r
423             else\r
424             {\r
425                 g.setClip(0, (int) clip.getBounds().getY(),\r
426                     cWidth * av.charWidth, (int) clip.getBounds().getHeight());\r
427             }\r
428 \r
429             if (av.vconsensus!=null && av.alignment.getWidth() >= av.vconsensus.size())\r
430             {\r
431               endx = av.vconsensus.size() - 2;\r
432             }\r
433 \r
434 \r
435             drawPanel(g, startRes, endx, 0, al.getHeight(), startRes, 0, ypos);\r
436             g.setClip(clip);\r
437             g.translate(-LABEL_WEST, 0);\r
438 \r
439             ypos += cHeight;\r
440             startRes += cWidth;\r
441             endx = (startRes + cWidth) - 1;\r
442 \r
443             if (endx > al.getWidth())\r
444             {\r
445                 endx = al.getWidth();\r
446             }\r
447         }\r
448     }\r
449 \r
450     /**\r
451      * DOCUMENT ME!\r
452      *\r
453      * @param g1 DOCUMENT ME!\r
454      * @param x1 DOCUMENT ME!\r
455      * @param x2 DOCUMENT ME!\r
456      * @param y1 DOCUMENT ME!\r
457      * @param y2 DOCUMENT ME!\r
458      * @param startx DOCUMENT ME!\r
459      * @param starty DOCUMENT ME!\r
460      * @param offset DOCUMENT ME!\r
461      */\r
462     synchronized public void drawPanel(Graphics g1, int x1, int x2, int y1,\r
463         int y2, int startx, int starty, int offset)\r
464     {\r
465         Graphics2D g = (Graphics2D) g1;\r
466         g.setFont(av.getFont());\r
467 \r
468         SequenceI nextSeq;\r
469 \r
470         /// First draw the sequences\r
471         /////////////////////////////\r
472         for (int i = y1; i < y2; i++)\r
473         {\r
474             nextSeq = av.alignment.getSequenceAt(i);\r
475 \r
476             sr.drawSequence(g, nextSeq, av.alignment.findAllGroups(nextSeq),\r
477                 x1, x2, (x1 - startx) * av.charWidth,\r
478                 offset + ((i - starty) * av.charHeight), av.charWidth,\r
479                 av.charHeight);\r
480 \r
481             if (av.showSequenceFeatures)\r
482             {\r
483                 fr.drawSequence(g, nextSeq,\r
484                     av.alignment.findAllGroups(nextSeq), x1, x2,\r
485                     (x1 - startx) * av.charWidth,\r
486                     offset + ((i - starty) * av.charHeight), av.charWidth,\r
487                     av.charHeight);\r
488             }\r
489         }\r
490 \r
491         //\r
492         /////////////////////////////////////\r
493         // Now outline any areas if necessary\r
494         /////////////////////////////////////\r
495         SequenceGroup group = av.getSelectionGroup();\r
496         java.util.Vector groups = av.alignment.getGroups();\r
497 \r
498         int sx = -1;\r
499         int sy = -1;\r
500         int ex = -1;\r
501         int groupIndex = -1;\r
502 \r
503         if ((group == null) && (groups.size() > 0))\r
504         {\r
505             group = (SequenceGroup) groups.elementAt(0);\r
506             groupIndex = 0;\r
507         }\r
508 \r
509 \r
510         if (group != null && !isOverview)\r
511         {\r
512             do\r
513             {\r
514                 int oldY = -1;\r
515                 int i = 0;\r
516                 boolean inGroup = false;\r
517                 int top = -1;\r
518                 int bottom = -1;\r
519 \r
520                 for (i = y1; i < y2; i++)\r
521                 {\r
522                     sx = (group.getStartRes() - startx) * av.charWidth;\r
523                     sy = offset + ((i - starty) * av.charHeight);\r
524                     ex = (((group.getEndRes() + 1) - group.getStartRes()) * av.charWidth) -\r
525                         1;\r
526 \r
527                     if(sx+ex<0 || sx>imgWidth)\r
528                     {\r
529                       continue;\r
530                     }\r
531 \r
532                     if ( (sx <= (x2-x1)*av.charWidth) &&\r
533                             group.sequences.contains(av.alignment.getSequenceAt(\r
534                                     i)))\r
535                     {\r
536                         if ((bottom == -1) &&\r
537                                 !group.sequences.contains(\r
538                                     av.alignment.getSequenceAt(i + 1)))\r
539                         {\r
540                             bottom = sy + av.charHeight;\r
541                         }\r
542 \r
543                         if (!inGroup)\r
544                         {\r
545                             if (((top == -1) && (i == 0)) ||\r
546                                     !group.sequences.contains(\r
547                                         av.alignment.getSequenceAt(i - 1)))\r
548                             {\r
549                                 top = sy;\r
550                             }\r
551 \r
552                             oldY = sy;\r
553                             inGroup = true;\r
554 \r
555                             if (group == av.getSelectionGroup())\r
556                             {\r
557                                 g.setStroke(new BasicStroke(1,\r
558                                         BasicStroke.CAP_BUTT,\r
559                                         BasicStroke.JOIN_ROUND, 3f,\r
560                                         new float[] { 5f, 3f }, 0f));\r
561                                 g.setColor(Color.RED);\r
562                             }\r
563                             else\r
564                             {\r
565                                 g.setStroke(new BasicStroke());\r
566                                 g.setColor(group.getOutlineColour());\r
567                             }\r
568                         }\r
569                     }\r
570                     else\r
571                     {\r
572                       if (inGroup)\r
573                       {\r
574                         if (sx >= 0 && sx < imgWidth)\r
575                           g.drawLine(sx, oldY, sx, sy);\r
576 \r
577                         if (sx + ex < imgWidth)\r
578                           g.drawLine(sx + ex, oldY, sx + ex, sy);\r
579 \r
580                         if (sx < 0)\r
581                         {\r
582                           ex += sx;\r
583                           sx = 0;\r
584                         }\r
585 \r
586                         if (sx + ex > imgWidth)\r
587                           ex = imgWidth;\r
588 \r
589                         else if (sx + ex >= (x2 - x1 + 1) * av.charWidth)\r
590                           ex = (x2 - x1 + 1) * av.charWidth;\r
591 \r
592                         if (top != -1)\r
593                         {\r
594                           g.drawLine(sx, top, sx + ex, top);\r
595                           top = -1;\r
596                         }\r
597 \r
598                         if (bottom != -1)\r
599                         {\r
600                           g.drawLine(sx, bottom, sx + ex, bottom);\r
601                           bottom = -1;\r
602                         }\r
603 \r
604                         inGroup = false;\r
605                         }\r
606                     }\r
607                 }\r
608 \r
609                 if (inGroup)\r
610                 {\r
611                   sy = offset + ( (i - starty) * av.charHeight);\r
612                   if (sx >= 0 && sx < imgWidth)\r
613                     g.drawLine(sx, oldY, sx, sy);\r
614 \r
615                   if (sx + ex < imgWidth)\r
616                     g.drawLine(sx + ex, oldY, sx + ex, sy);\r
617 \r
618                   if (sx < 0)\r
619                   {\r
620                     ex += sx;\r
621                     sx = 0;\r
622                   }\r
623 \r
624                   if (sx + ex > imgWidth)\r
625                     ex = imgWidth;\r
626                   else if (sx + ex >= (x2 - x1 + 1) * av.charWidth)\r
627                     ex = (x2 - x1 + 1) * av.charWidth;\r
628 \r
629                   if (top != -1)\r
630                   {\r
631                     g.drawLine(sx, top, sx + ex, top);\r
632                     top = -1;\r
633                   }\r
634 \r
635                   if (bottom != -1)\r
636                   {\r
637                     g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);\r
638                     bottom = -1;\r
639                   }\r
640 \r
641                     inGroup = false;\r
642                 }\r
643 \r
644                 groupIndex++;\r
645 \r
646                 if (groupIndex >= groups.size())\r
647                 {\r
648                     break;\r
649                 }\r
650 \r
651                 group = (SequenceGroup) groups.elementAt(groupIndex);\r
652             }\r
653             while (groupIndex < groups.size());\r
654         }\r
655 \r
656         /// Highlight search Results once all sequences have been drawn\r
657         //////////////////////////////////////////////////////////\r
658         if (displaySearch)\r
659         {\r
660             for (int r = 0; r < searchResults.length; r += 3)\r
661             {\r
662                 int searchSeq = searchResults[r];\r
663 \r
664                 if ((searchSeq >= y1) && (searchSeq < y2))\r
665                 {\r
666                     SequenceI seq = av.getAlignment().getSequenceAt(searchSeq);\r
667 \r
668                     int searchStart = seq.findIndex(searchResults[r + 1]) - 1;\r
669                     int searchEnd = seq.findIndex(searchResults[r + 2]) - 1;\r
670 \r
671                     SequenceRenderer ssr = (SequenceRenderer) sr;\r
672 \r
673                     if (searchStart < x1)\r
674                     {\r
675                         searchStart = x1;\r
676                     }\r
677 \r
678                     if (searchEnd > x2)\r
679                     {\r
680                         searchEnd = x2;\r
681                     }\r
682 \r
683                     ssr.drawHighlightedText(seq, searchStart, searchEnd,\r
684                         (searchStart - startx) * av.charWidth,\r
685                         offset + ((searchSeq - starty) * av.charHeight),\r
686                         av.charWidth, av.charHeight);\r
687                 }\r
688             }\r
689         }\r
690     }\r
691 \r
692     /**\r
693      * DOCUMENT ME!\r
694      *\r
695      * @param results DOCUMENT ME!\r
696      */\r
697     public void highlightSearchResults(int[] results)\r
698     {\r
699         // results are in the order sequence, startRes, endRes\r
700         if (results == null)\r
701         {\r
702             displaySearch = false;\r
703         }\r
704         else\r
705         {\r
706             displaySearch = true;\r
707         }\r
708 \r
709         searchResults = results;\r
710 \r
711         repaint();\r
712     }\r
713 }\r