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