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