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