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