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