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