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