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