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