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