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