null pointer fix.
[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 (img!=null\r
223         && (fastPaint  ||  getWidth() !=  g.getClipBounds().width\r
224             ||  getHeight() != g.getClipBounds().height))\r
225     {\r
226       g.drawImage(img, 0, 0, this);\r
227       fastPaint = false;\r
228       return;\r
229     }\r
230 \r
231     // this draws the whole of the alignment\r
232     imgWidth = getWidth();\r
233     imgHeight = getHeight();\r
234 \r
235     imgWidth -= (imgWidth % av.charWidth);\r
236     imgHeight -= (imgHeight % av.charHeight);\r
237 \r
238     if ( (imgWidth < 1) || (imgHeight < 1))\r
239     {\r
240       return;\r
241     }\r
242 \r
243     img = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);\r
244     gg = (Graphics2D) img.getGraphics();\r
245     gg.setFont(av.getFont());\r
246     gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,\r
247                         RenderingHints.VALUE_ANTIALIAS_ON);\r
248 \r
249     gg.setColor(Color.white);\r
250     gg.fillRect(0, 0, imgWidth, imgHeight);\r
251 \r
252     chunkWidth = getWrappedCanvasWidth(getWidth());\r
253     chunkHeight = (av.getAlignment().getHeight() + 2) * av.charHeight;\r
254 \r
255     av.setChunkHeight(chunkHeight);\r
256     av.setChunkWidth(chunkWidth);\r
257 \r
258     if (av.getWrapAlignment())\r
259     {\r
260       drawWrappedPanel(gg, getWidth(), getHeight(), av.startRes);\r
261     }\r
262     else\r
263     {\r
264       drawPanel(gg, av.startRes, av.endRes, av.startSeq, av.endSeq,\r
265                 av.startRes, av.startSeq, 0);\r
266     }\r
267 \r
268     g.drawImage(img, 0, 0, this);\r
269   }\r
270 \r
271   public int getWrappedCanvasWidth(int cwidth)\r
272   {\r
273     FontMetrics fm = getFontMetrics(av.getFont());\r
274 \r
275     LABEL_EAST = 0;\r
276     LABEL_WEST = 0;\r
277 \r
278     if (av.scaleRightWrapped)\r
279     {\r
280       LABEL_EAST = fm.stringWidth(av.alignment.getWidth() + "000");\r
281     }\r
282 \r
283     if (av.scaleLeftWrapped)\r
284     {\r
285       LABEL_WEST = fm.stringWidth(av.alignment.getWidth() + "");\r
286     }\r
287 \r
288     return (cwidth - LABEL_EAST - LABEL_WEST) / av.charWidth;\r
289   }\r
290 \r
291   public void drawWrappedPanel(Graphics g, int canvasWidth, int canvasHeight,\r
292                                int startRes)\r
293   {\r
294     AlignmentI al = av.getAlignment();\r
295 \r
296     FontMetrics fm = getFontMetrics(av.getFont());\r
297 \r
298     int LABEL_EAST = 0;\r
299 \r
300     if (av.scaleRightWrapped)\r
301     {\r
302       LABEL_EAST = fm.stringWidth(al.getWidth() + "000");\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(al.getWidth() + "0");\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 + av.charWidth, 0);\r
332         drawEastScale(g, startRes, endx, ypos);\r
333         g.translate( - (canvasWidth - LABEL_EAST + av.charWidth), 0);\r
334       }\r
335 \r
336       g.translate(LABEL_WEST, 0);\r
337 \r
338       if (av.scaleAboveWrapped)\r
339       {\r
340         drawNorthScale(g, startRes, endx, ypos);\r
341       }\r
342 \r
343       // When printing we have an extra clipped region,\r
344       // the Printable page which we need to account for here\r
345       Shape clip = g.getClip();\r
346 \r
347       if (clip == null)\r
348       {\r
349         g.setClip(0, 0, cWidth * av.charWidth, canvasHeight);\r
350       }\r
351       else\r
352       {\r
353         g.setClip(0, (int) clip.getBounds().getY(),\r
354                   cWidth * av.charWidth, (int) clip.getBounds().getHeight());\r
355       }\r
356 \r
357       drawPanel(g, startRes, endx, 0, al.getHeight(), startRes, 0, ypos);\r
358       g.setClip(clip);\r
359       g.translate( -LABEL_WEST, 0);\r
360 \r
361       ypos += cHeight;\r
362       startRes += cWidth;\r
363       endx = (startRes + cWidth) - 1;\r
364 \r
365       if (endx > al.getWidth())\r
366       {\r
367         endx = al.getWidth();\r
368       }\r
369     }\r
370   }\r
371 \r
372   synchronized public void drawPanel(Graphics g1, int x1, int x2, int y1,\r
373                                      int y2, int startx, int starty, int offset)\r
374   {\r
375     Graphics2D g = (Graphics2D) g1;\r
376     g.setFont(av.getFont());\r
377     sr.renderGaps(av.renderGaps);\r
378 \r
379     SequenceI nextSeq;\r
380 \r
381     /// First draw the sequences\r
382     /////////////////////////////\r
383     for (int i = y1; i < y2; i++)\r
384     {\r
385       nextSeq = av.alignment.getSequenceAt(i);\r
386 \r
387       sr.drawSequence(g, nextSeq, av.alignment.findAllGroups(nextSeq),\r
388                       x1, x2, (x1 - startx) * av.charWidth,\r
389                       offset +\r
390                       AlignmentUtil.getPixelHeight(starty, i, av.charHeight),\r
391                       av.charWidth, av.charHeight);\r
392 \r
393       if (av.showSequenceFeatures)\r
394       {\r
395         fr.drawSequence(g, nextSeq,\r
396                         av.alignment.findAllGroups(nextSeq), x1, x2,\r
397                         (x1 - startx) * av.charWidth,\r
398                         offset +\r
399                         AlignmentUtil.getPixelHeight(starty, i, av.charHeight),\r
400                         av.charWidth, av.charHeight);\r
401       }\r
402     }\r
403 \r
404     //\r
405     /////////////////////////////////////\r
406     // Now outline any areas if necessary\r
407     /////////////////////////////////////\r
408     SequenceGroup group = av.getSelectionGroup();\r
409     java.util.Vector groups = av.alignment.getGroups();\r
410 \r
411     int sx = -1;\r
412     int sy = -1;\r
413     int ex = -1;\r
414     int groupIndex = -1;\r
415 \r
416     if ( (group == null) && (groups.size() > 0))\r
417     {\r
418       group = (SequenceGroup) groups.elementAt(0);\r
419       groupIndex = 0;\r
420     }\r
421 \r
422     if (group != null)\r
423     {\r
424       do\r
425       {\r
426         int oldY = -1;\r
427         int i = 0;\r
428         boolean inGroup = false;\r
429         int top = -1;\r
430         int bottom = -1;\r
431 \r
432         for (i = y1; i < y2; i++)\r
433         {\r
434           sx = (group.getStartRes() - startx) * av.charWidth;\r
435           sy = offset +\r
436               AlignmentUtil.getPixelHeight(starty, i, av.charHeight);\r
437           ex = ( ( (group.getEndRes() + 1) - group.getStartRes()) *\r
438                 av.charWidth) -\r
439               1;\r
440 \r
441           if ( (sx < getWidth()) && (ex > 0) &&\r
442               group.sequences.contains(av.alignment.getSequenceAt(\r
443                   i)))\r
444           {\r
445             if ( (bottom == -1) &&\r
446                 !group.sequences.contains(\r
447                     av.alignment.getSequenceAt(i + 1)))\r
448             {\r
449               bottom = sy + av.charHeight;\r
450             }\r
451 \r
452             if (!inGroup)\r
453             {\r
454               if ( ( (top == -1) && (i == 0)) ||\r
455                   !group.sequences.contains(\r
456                       av.alignment.getSequenceAt(i - 1)))\r
457               {\r
458                 top = sy;\r
459               }\r
460 \r
461               oldY = sy;\r
462               inGroup = true;\r
463 \r
464               if (group == av.getSelectionGroup())\r
465               {\r
466                 g.setStroke(new BasicStroke(1,\r
467                                             BasicStroke.CAP_BUTT,\r
468                                             BasicStroke.JOIN_ROUND, 3f,\r
469                                             new float[]\r
470                                             {5f, 3f}, 0f));\r
471                 g.setColor(Color.RED);\r
472               }\r
473               else\r
474               {\r
475                 g.setStroke(new BasicStroke());\r
476                 g.setColor(group.getOutlineColour());\r
477               }\r
478             }\r
479           }\r
480           else\r
481           {\r
482             if (inGroup)\r
483             {\r
484               g.drawLine(sx, oldY, sx, sy);\r
485               g.drawLine(sx + ex, oldY, sx + ex, sy);\r
486 \r
487               if (top != -1)\r
488               {\r
489                 g.drawLine(sx, top, sx + ex, top);\r
490                 top = -1;\r
491               }\r
492 \r
493               if (bottom != -1)\r
494               {\r
495                 g.drawLine(sx, bottom, sx + ex, bottom);\r
496                 bottom = -1;\r
497               }\r
498 \r
499               inGroup = false;\r
500             }\r
501           }\r
502         }\r
503 \r
504         if (inGroup)\r
505         {\r
506           if (top != -1)\r
507           {\r
508             g.drawLine(sx, top, sx + ex, top);\r
509             top = -1;\r
510           }\r
511 \r
512           if (bottom != -1)\r
513           {\r
514             g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);\r
515             bottom = -1;\r
516           }\r
517 \r
518           sy = offset +\r
519               AlignmentUtil.getPixelHeight(starty, i, av.charHeight);\r
520           g.drawLine(sx, oldY, sx, sy);\r
521           g.drawLine(sx + ex, oldY, sx + ex, sy);\r
522           inGroup = false;\r
523         }\r
524 \r
525         groupIndex++;\r
526 \r
527         if (groupIndex >= groups.size())\r
528         {\r
529           break;\r
530         }\r
531 \r
532         group = (SequenceGroup) groups.elementAt(groupIndex);\r
533       }\r
534       while (groupIndex < groups.size());\r
535     }\r
536 \r
537     /// Highlight search Results once all sequences have been drawn\r
538     //////////////////////////////////////////////////////////\r
539     if (displaySearch)\r
540     {\r
541       for (int r = 0; r < searchResults.length; r += 3)\r
542       {\r
543         int searchSeq = searchResults[r];\r
544 \r
545         if ( (searchSeq >= y1) && (searchSeq < y2))\r
546         {\r
547           SequenceI seq = av.getAlignment().getSequenceAt(searchSeq);\r
548 \r
549           int searchStart = seq.findIndex(searchResults[r + 1]) - 1;\r
550           int searchEnd = seq.findIndex(searchResults[r + 2]) - 1;\r
551 \r
552           SequenceRenderer ssr = (SequenceRenderer) sr;\r
553 \r
554           if (searchStart < x1)\r
555           {\r
556             searchStart = x1;\r
557           }\r
558 \r
559           if (searchEnd > x2)\r
560           {\r
561             searchEnd = x2;\r
562           }\r
563 \r
564           ssr.drawHighlightedText(seq, searchStart, searchEnd,\r
565                                   (searchStart - startx) * av.charWidth,\r
566                                   offset +\r
567                                   AlignmentUtil.getPixelHeight(starty,\r
568               searchSeq,\r
569               av.charHeight), av.charWidth, av.charHeight);\r
570         }\r
571       }\r
572     }\r
573   }\r
574 \r
575   public void highlightSearchResults(int[] results)\r
576   {\r
577     // results are in the order sequence, startRes, endRes\r
578     if (results == null)\r
579     {\r
580       displaySearch = false;\r
581     }\r
582     else\r
583     {\r
584       displaySearch = true;\r
585     }\r
586 \r
587     searchResults = results;\r
588 \r
589     repaint();\r
590   }\r
591 }\r