Wrap alignment with annotations
[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 \r
245     if (av.getWrapAlignment())\r
246     {\r
247       drawWrappedPanel(gg, imgWidth, imgHeight, av.startRes);\r
248     }\r
249     else\r
250     {\r
251       drawPanel(gg, av.startRes, av.endRes, av.startSeq, av.endSeq, av.startRes,\r
252                 av.startSeq, 0);\r
253     }\r
254 \r
255     g.drawImage(img, 0, 0, this);\r
256 \r
257   }\r
258 \r
259   int LABEL_WEST, LABEL_EAST;\r
260   public int getWrappedCanvasWidth(int cwidth)\r
261   {\r
262       FontMetrics fm = getFontMetrics(av.getFont());\r
263 \r
264       LABEL_EAST = 0;\r
265       LABEL_WEST = 0;\r
266 \r
267       if (av.scaleRightWrapped)\r
268       {\r
269           LABEL_EAST = fm.stringWidth(getMask()+"0");\r
270       }\r
271 \r
272       if (av.scaleLeftWrapped)\r
273       {\r
274           LABEL_WEST = fm.stringWidth(getMask());\r
275       }\r
276 \r
277       return (cwidth - LABEL_EAST - LABEL_WEST) / av.charWidth;\r
278   }\r
279 \r
280 \r
281   /**\r
282    * Generates a string of zeroes.\r
283    * @return String\r
284    */\r
285   String getMask()\r
286   {\r
287     String mask = "0";\r
288     for (int i = av.alignment.getWidth(); i > 0; i /= 10)\r
289     {\r
290       mask += "0";\r
291     }\r
292     return mask;\r
293     }\r
294 \r
295   public void drawWrappedPanel(Graphics g, int canvasWidth, int canvasHeight,\r
296                                int startRes)\r
297   {\r
298     AlignmentI al = av.getAlignment();\r
299 \r
300     FontMetrics fm = getFontMetrics(av.getFont());\r
301 \r
302     int LABEL_EAST = 0;\r
303 \r
304     if (av.scaleRightWrapped)\r
305     {\r
306         LABEL_EAST = fm.stringWidth(getMask()+"0");\r
307     }\r
308 \r
309     int LABEL_WEST = 0;\r
310 \r
311     if (av.scaleLeftWrapped)\r
312     {\r
313         LABEL_WEST = fm.stringWidth(getMask());\r
314     }\r
315 \r
316     int hgap = av.charHeight;\r
317     if(av.scaleAboveWrapped)\r
318       hgap += av.charHeight;\r
319 \r
320     int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / av.charWidth;\r
321     int cHeight = av.getAlignment().getHeight() * av.charHeight;\r
322 \r
323     av.setWrappedWidth(cWidth);\r
324 \r
325     av.endRes = av.startRes + cWidth;\r
326 \r
327 \r
328     int endx = (startRes + cWidth) - 1;\r
329     int ypos = hgap;\r
330 \r
331 \r
332     while ((ypos <= canvasHeight) && (startRes < av.alignment.getWidth()))\r
333     {\r
334         g.setColor(Color.black);\r
335 \r
336         if (av.scaleLeftWrapped)\r
337         {\r
338             drawWestScale(g, startRes, endx, ypos);\r
339         }\r
340 \r
341         if (av.scaleRightWrapped)\r
342         {\r
343             g.translate(canvasWidth - LABEL_EAST, 0);\r
344             drawEastScale(g, startRes, endx, ypos);\r
345             g.translate(-(canvasWidth - LABEL_EAST), 0);\r
346         }\r
347 \r
348         g.translate(LABEL_WEST, 0);\r
349 \r
350         if (av.scaleAboveWrapped)\r
351         {\r
352             drawNorthScale(g, startRes, endx, ypos);\r
353         }\r
354 \r
355 \r
356         if (av.vconsensus!=null && av.alignment.getWidth() >= av.vconsensus.size())\r
357         {\r
358           endx = av.vconsensus.size() - 2;\r
359         }\r
360 \r
361 \r
362         drawPanel(g, startRes, endx, 0, al.getHeight(), startRes, 0, ypos);\r
363 \r
364         if(av.showAnnotation)\r
365         {\r
366           g.translate(0, cHeight + ypos);\r
367           if(annotations==null)\r
368             annotations = new AnnotationPanel(av);\r
369 \r
370           annotations.drawComponent( g, startRes, endx + 1);\r
371           g.translate(0, -cHeight - ypos);\r
372         }\r
373         g.translate(-LABEL_WEST, 0);\r
374 \r
375         ypos += cHeight+getAnnotationHeight()+hgap;\r
376 \r
377 \r
378         startRes += cWidth;\r
379         endx = (startRes + cWidth) - 1;\r
380 \r
381         if (endx > al.getWidth())\r
382         {\r
383             endx = al.getWidth();\r
384         }\r
385         }\r
386 \r
387   }\r
388 \r
389   AnnotationPanel annotations;\r
390   int getAnnotationHeight()\r
391   {\r
392     if(!av.showAnnotation)\r
393       return 0;\r
394 \r
395     if(annotations==null)\r
396       annotations = new AnnotationPanel(av);\r
397 \r
398     return annotations.adjustPanelHeight();\r
399     }\r
400 \r
401   synchronized public void drawPanel(Graphics g, int x1, int x2, int y1, int y2,\r
402                                      int startx, int starty, int offset)\r
403   {\r
404 \r
405     g.setFont(av.getFont());\r
406     sr.renderGaps(av.renderGaps);\r
407 \r
408     SequenceI nextSeq;\r
409     /// First draw the sequences\r
410   /////////////////////////////\r
411   for (int i = y1; i < y2; i++)\r
412   {\r
413     nextSeq = av.alignment.getSequenceAt(i);\r
414 \r
415     sr.drawSequence(g, nextSeq, av.alignment.findAllGroups(nextSeq), x1, x2,\r
416                     (x1 - startx) * av.charWidth,\r
417                     offset +\r
418                     (i - starty) * av.charHeight,\r
419                     av.charWidth, av.charHeight);\r
420 \r
421     if (av.showSequenceFeatures)\r
422     {\r
423       fr.drawSequence(g, nextSeq, av.alignment.findAllGroups(nextSeq), x1, x2,\r
424                       (x1 - startx) * av.charWidth,\r
425                       offset +\r
426                       (i - starty) * av.charHeight,\r
427                       av.charWidth, av.charHeight);\r
428     }\r
429   }\r
430 \r
431     /////////////////////////////////////\r
432 \r
433     // Now outline any areas if necessary\r
434     /////////////////////////////////////\r
435     SequenceGroup group = av.getSelectionGroup();\r
436     java.util.Vector groups = av.alignment.getGroups();\r
437 \r
438     int sx = -1, sy = -1, ex = -1;\r
439     int groupIndex = -1;\r
440     if (group == null && groups.size() > 0)\r
441     {\r
442       group = (SequenceGroup) groups.elementAt(0);\r
443       groupIndex = 0;\r
444     }\r
445     if (group != null && !isOverview)\r
446     {\r
447         do\r
448         {\r
449             int oldY = -1;\r
450             int i = 0;\r
451             boolean inGroup = false;\r
452             int top = -1;\r
453             int bottom = -1;\r
454 \r
455             for (i = y1; i < y2; i++)\r
456             {\r
457                 sx = (group.getStartRes() - startx) * av.charWidth;\r
458                 sy = offset + ((i - starty) * av.charHeight);\r
459                 ex = (((group.getEndRes() + 1) - group.getStartRes()) * av.charWidth) -\r
460                     1;\r
461 \r
462                 if(sx+ex<0 || sx>imgWidth)\r
463                 {\r
464                   continue;\r
465                 }\r
466 \r
467                 if ( (sx <= (x2-x1)*av.charWidth) &&\r
468                         group.sequences.contains(av.alignment.getSequenceAt(\r
469                                 i)))\r
470                 {\r
471                     if ((bottom == -1) &&\r
472                             !group.sequences.contains(\r
473                                 av.alignment.getSequenceAt(i + 1)))\r
474                     {\r
475                         bottom = sy + av.charHeight;\r
476                     }\r
477 \r
478                     if (!inGroup)\r
479                     {\r
480                         if (((top == -1) && (i == 0)) ||\r
481                                 !group.sequences.contains(\r
482                                     av.alignment.getSequenceAt(i - 1)))\r
483                         {\r
484                             top = sy;\r
485                         }\r
486 \r
487                         oldY = sy;\r
488                         inGroup = true;\r
489 \r
490                         if (group == av.getSelectionGroup())\r
491                         {\r
492 \r
493                             g.setColor(Color.red);\r
494                         }\r
495                         else\r
496                         {\r
497                             g.setColor(group.getOutlineColour());\r
498                         }\r
499                     }\r
500                 }\r
501                 else\r
502                 {\r
503                   if (inGroup)\r
504                   {\r
505                     if (sx >= 0 && sx < imgWidth)\r
506                       g.drawLine(sx, oldY, sx, sy);\r
507 \r
508                     if (sx + ex < imgWidth)\r
509                       g.drawLine(sx + ex, oldY, sx + ex, sy);\r
510 \r
511                     if (sx < 0)\r
512                     {\r
513                       ex += sx;\r
514                       sx = 0;\r
515                     }\r
516 \r
517                     if (sx + ex > imgWidth)\r
518                       ex = imgWidth;\r
519 \r
520                     else if (sx + ex >= (x2 - x1 + 1) * av.charWidth)\r
521                       ex = (x2 - x1 + 1) * av.charWidth;\r
522 \r
523                     if (top != -1)\r
524                     {\r
525                       g.drawLine(sx, top, sx + ex, top);\r
526                       top = -1;\r
527                     }\r
528 \r
529                     if (bottom != -1)\r
530                     {\r
531                       g.drawLine(sx, bottom, sx + ex, bottom);\r
532                       bottom = -1;\r
533                     }\r
534 \r
535                     inGroup = false;\r
536                     }\r
537                 }\r
538             }\r
539 \r
540             if (inGroup)\r
541             {\r
542               sy = offset + ( (i - starty) * av.charHeight);\r
543               if (sx >= 0 && sx < imgWidth)\r
544                 g.drawLine(sx, oldY, sx, sy);\r
545 \r
546               if (sx + ex < imgWidth)\r
547                 g.drawLine(sx + ex, oldY, sx + ex, sy);\r
548 \r
549               if (sx < 0)\r
550               {\r
551                 ex += sx;\r
552                 sx = 0;\r
553               }\r
554 \r
555               if (sx + ex > imgWidth)\r
556                 ex = imgWidth;\r
557               else if (sx + ex >= (x2 - x1 + 1) * av.charWidth)\r
558                 ex = (x2 - x1 + 1) * av.charWidth;\r
559 \r
560               if (top != -1)\r
561               {\r
562                 g.drawLine(sx, top, sx + ex, top);\r
563                 top = -1;\r
564               }\r
565 \r
566               if (bottom != -1)\r
567               {\r
568                 g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);\r
569                 bottom = -1;\r
570               }\r
571 \r
572                 inGroup = false;\r
573             }\r
574 \r
575             groupIndex++;\r
576 \r
577             if (groupIndex >= groups.size())\r
578             {\r
579                 break;\r
580             }\r
581 \r
582             group = (SequenceGroup) groups.elementAt(groupIndex);\r
583         }\r
584         while (groupIndex < groups.size());\r
585     }\r
586 \r
587     /// Highlight search Results once all sequences have been drawn\r
588     //////////////////////////////////////////////////////////\r
589     if (displaySearch)\r
590     {\r
591       for (int r = 0; r < searchResults.length; r += 3)\r
592       {\r
593         int searchSeq = searchResults[r];\r
594 \r
595         if (searchSeq >= y1 && searchSeq < y2)\r
596         {\r
597           SequenceI seq = av.getAlignment().getSequenceAt(searchSeq);\r
598 \r
599           int searchStart = seq.findIndex(searchResults[r + 1]) - 1;\r
600           int searchEnd = seq.findIndex(searchResults[r + 2]) - 1;\r
601 \r
602           SequenceRenderer ssr = (SequenceRenderer) sr;\r
603           if (searchStart < x1)\r
604           {\r
605             searchStart = x1;\r
606           }\r
607           if (searchEnd > x2)\r
608           {\r
609             searchEnd = x2;\r
610           }\r
611 \r
612           ssr.drawHighlightedText(seq,\r
613                                   searchStart,\r
614                                   searchEnd,\r
615                                   (searchStart - startx) * av.charWidth,\r
616                                   offset +\r
617                                   (searchSeq-starty)*av.charHeight,\r
618                                   av.charWidth,\r
619                                   av.charHeight);\r
620         }\r
621       }\r
622     }\r
623 \r
624   }\r
625 \r
626   public void highlightSearchResults(int[] results)\r
627   {\r
628     // results are in the order sequence, startRes, endRes\r
629     if (results == null)\r
630     {\r
631       displaySearch = false;\r
632     }\r
633     else\r
634     {\r
635       displaySearch = true;\r
636     }\r
637 \r
638     searchResults = results;\r
639 \r
640     repaint();\r
641   }\r
642 \r
643 }\r