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