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