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