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