68b8501add141188e5f816a788c64df7bd408e2c
[jalview.git] / src / jalview / appletgui / SeqCanvas.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4)
3  * Copyright (C) 2008 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  * 
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.appletgui;
20
21 import java.awt.*;
22
23 import jalview.datamodel.*;
24
25 public class SeqCanvas extends Panel
26 {
27   FeatureRenderer fr;
28
29   SequenceRenderer sr;
30
31   Image img;
32
33   Graphics gg;
34
35   int imgWidth;
36
37   int imgHeight;
38
39   AlignViewport av;
40
41   SearchResults searchResults = null;
42
43   boolean fastPaint = false;
44
45   int cursorX = 0;
46
47   int cursorY = 0;
48
49   public SeqCanvas(AlignViewport av)
50   {
51     this.av = av;
52     fr = new FeatureRenderer(av);
53     sr = new SequenceRenderer(av);
54     PaintRefresher.Register(this, av.getSequenceSetId());
55   }
56
57   public AlignViewport getViewport()
58   {
59     return av;
60   }
61
62   public FeatureRenderer getFeatureRenderer()
63   {
64     return fr;
65   }
66
67   public SequenceRenderer getSequenceRenderer()
68   {
69     return sr;
70   }
71
72   void drawNorthScale(Graphics g, int startx, int endx, int ypos)
73   {
74     int scalestartx = startx - startx % 10 + 10;
75
76     g.setColor(Color.black);
77
78     // NORTH SCALE
79     for (int i = scalestartx; i < endx; i += 10)
80     {
81       int value = i;
82       if (av.hasHiddenColumns)
83       {
84         value = av.getColumnSelection().adjustForHiddenColumns(value);
85       }
86
87       g.drawString(String.valueOf(value), (i - startx - 1) * av.charWidth,
88               ypos - (av.charHeight / 2));
89
90       g.drawLine(((i - startx - 1) * av.charWidth) + (av.charWidth / 2),
91               (ypos + 2) - (av.charHeight / 2),
92               ((i - startx - 1) * av.charWidth) + (av.charWidth / 2),
93               ypos - 2);
94     }
95   }
96
97   void drawWestScale(Graphics g, int startx, int endx, int ypos)
98   {
99     FontMetrics fm = getFontMetrics(av.getFont());
100     ypos += av.charHeight;
101     if (av.hasHiddenColumns)
102     {
103       startx = av.getColumnSelection().adjustForHiddenColumns(startx);
104       endx = av.getColumnSelection().adjustForHiddenColumns(endx);
105     }
106
107     int maxwidth = av.alignment.getWidth();
108     if (av.hasHiddenColumns)
109     {
110       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
111     }
112
113     // WEST SCALE
114     for (int i = 0; i < av.alignment.getHeight(); i++)
115     {
116       SequenceI seq = av.alignment.getSequenceAt(i);
117       int index = startx;
118       int value = -1;
119
120       while (index < endx)
121       {
122         if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
123         {
124           index++;
125
126           continue;
127         }
128
129         value = av.alignment.getSequenceAt(i).findPosition(index);
130
131         break;
132       }
133
134       if (value != -1)
135       {
136         int x = LABEL_WEST - fm.stringWidth(String.valueOf(value))
137                 - av.charWidth / 2;
138         g.drawString(value + "", x, (ypos + (i * av.charHeight))
139                 - (av.charHeight / 5));
140       }
141     }
142   }
143
144   void drawEastScale(Graphics g, int startx, int endx, int ypos)
145   {
146     ypos += av.charHeight;
147
148     if (av.hasHiddenColumns)
149     {
150       endx = av.getColumnSelection().adjustForHiddenColumns(endx);
151     }
152
153     SequenceI seq;
154     // EAST SCALE
155     for (int i = 0; i < av.alignment.getHeight(); i++)
156     {
157       seq = av.alignment.getSequenceAt(i);
158       int index = endx;
159       int value = -1;
160
161       while (index > startx)
162       {
163         if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
164         {
165           index--;
166
167           continue;
168         }
169
170         value = seq.findPosition(index);
171
172         break;
173       }
174
175       if (value != -1)
176       {
177         g.drawString(String.valueOf(value), 0, (ypos + (i * av.charHeight))
178                 - (av.charHeight / 5));
179       }
180     }
181   }
182
183   int lastsr = 0;
184
185   void fastPaint(int horizontal, int vertical)
186   {
187     if (fastPaint || gg == null)
188     {
189       return;
190     }
191
192     // Its possible on certain browsers that the call to fastpaint
193     // is faster than it can paint, so this check here catches
194     // this possibility
195     if (lastsr + horizontal != av.startRes)
196     {
197       horizontal = av.startRes - lastsr;
198     }
199
200     lastsr = av.startRes;
201
202     fastPaint = true;
203     gg.copyArea(horizontal * av.charWidth, vertical * av.charHeight,
204             imgWidth - horizontal * av.charWidth, imgHeight - vertical
205                     * av.charHeight, -horizontal * av.charWidth, -vertical
206                     * av.charHeight);
207
208     int sr = av.startRes, er = av.endRes, ss = av.startSeq, es = av.endSeq, transX = 0, transY = 0;
209
210     if (horizontal > 0) // scrollbar pulled right, image to the left
211     {
212       transX = (er - sr - horizontal) * av.charWidth;
213       sr = er - horizontal;
214     }
215     else if (horizontal < 0)
216     {
217       er = sr - horizontal;
218     }
219
220     else if (vertical > 0) // scroll down
221     {
222       ss = es - vertical;
223       if (ss < av.startSeq) // ie scrolling too fast, more than a page at a time
224       {
225         ss = av.startSeq;
226       }
227       else
228       {
229         transY = imgHeight - vertical * av.charHeight;
230       }
231     }
232     else if (vertical < 0)
233     {
234       es = ss - vertical;
235       if (es > av.endSeq)
236       {
237         es = av.endSeq;
238       }
239     }
240
241     gg.translate(transX, transY);
242
243     drawPanel(gg, sr, er, ss, es, 0);
244     gg.translate(-transX, -transY);
245
246     repaint();
247
248   }
249
250   /**
251    * Definitions of startx and endx (hopefully): SMJS This is what I'm working
252    * towards! startx is the first residue (starting at 0) to display. endx is
253    * the last residue to display (starting at 0). starty is the first sequence
254    * to display (starting at 0). endy is the last sequence to display (starting
255    * at 0). NOTE 1: The av limits are set in setFont in this class and in the
256    * adjustment listener in SeqPanel when the scrollbars move.
257    */
258   public void update(Graphics g)
259   {
260     paint(g);
261   }
262
263   public void paint(Graphics g)
264   {
265
266     if (img != null
267             && (fastPaint || (getSize().width != g.getClipBounds().width) || (getSize().height != g
268                     .getClipBounds().height)))
269     {
270       g.drawImage(img, 0, 0, this);
271       fastPaint = false;
272       return;
273     }
274
275     if (fastPaint)
276     {
277       g.drawImage(img, 0, 0, this);
278       fastPaint = false;
279       return;
280     }
281
282     // this draws the whole of the alignment
283     imgWidth = this.getSize().width;
284     imgHeight = this.getSize().height;
285
286     imgWidth -= imgWidth % av.charWidth;
287     imgHeight -= imgHeight % av.charHeight;
288
289     if (imgWidth < 1 || imgHeight < 1)
290     {
291       return;
292     }
293
294     if (img == null || imgWidth != img.getWidth(this)
295             || imgHeight != img.getHeight(this))
296     {
297       img = createImage(imgWidth, imgHeight);
298       gg = img.getGraphics();
299       gg.setFont(av.getFont());
300     }
301
302     gg.setColor(Color.white);
303     gg.fillRect(0, 0, imgWidth, imgHeight);
304
305     if (av.getWrapAlignment())
306     {
307       drawWrappedPanel(gg, imgWidth, imgHeight, av.startRes);
308     }
309     else
310     {
311       drawPanel(gg, av.startRes, av.endRes, av.startSeq, av.endSeq, 0);
312     }
313
314     g.drawImage(img, 0, 0, this);
315
316   }
317
318   int LABEL_WEST, LABEL_EAST;
319
320   public int getWrappedCanvasWidth(int cwidth)
321   {
322     cwidth -= cwidth % av.charWidth;
323
324     FontMetrics fm = getFontMetrics(av.getFont());
325
326     LABEL_EAST = 0;
327     LABEL_WEST = 0;
328
329     if (av.scaleRightWrapped)
330     {
331       LABEL_EAST = fm.stringWidth(getMask());
332     }
333
334     if (av.scaleLeftWrapped)
335     {
336       LABEL_WEST = fm.stringWidth(getMask());
337     }
338
339     return (cwidth - LABEL_EAST - LABEL_WEST) / av.charWidth;
340   }
341
342   /**
343    * Generates a string of zeroes.
344    * 
345    * @return String
346    */
347   String getMask()
348   {
349     String mask = "0";
350     int maxWidth = 0;
351     int tmp;
352     for (int i = 0; i < av.alignment.getHeight(); i++)
353     {
354       tmp = av.alignment.getSequenceAt(i).getEnd();
355       if (tmp > maxWidth)
356       {
357         maxWidth = tmp;
358       }
359     }
360
361     for (int i = maxWidth; i > 0; i /= 10)
362     {
363       mask += "0";
364     }
365     return mask;
366   }
367
368   public void drawWrappedPanel(Graphics g, int canvasWidth,
369           int canvasHeight, int startRes)
370   {
371     AlignmentI al = av.getAlignment();
372
373     FontMetrics fm = getFontMetrics(av.getFont());
374
375     if (av.scaleRightWrapped)
376     {
377       LABEL_EAST = fm.stringWidth(getMask());
378     }
379
380     if (av.scaleLeftWrapped)
381     {
382       LABEL_WEST = fm.stringWidth(getMask());
383     }
384
385     int hgap = av.charHeight;
386     if (av.scaleAboveWrapped)
387     {
388       hgap += av.charHeight;
389     }
390
391     int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / av.charWidth;
392     int cHeight = av.getAlignment().getHeight() * av.charHeight;
393
394     av.setWrappedWidth(cWidth);
395
396     av.endRes = av.startRes + cWidth;
397
398     int endx;
399     int ypos = hgap;
400
401     int maxwidth = av.alignment.getWidth() - 1;
402
403     if (av.hasHiddenColumns)
404     {
405       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
406     }
407
408     while ((ypos <= canvasHeight) && (startRes < maxwidth))
409     {
410       endx = startRes + cWidth - 1;
411
412       if (endx > maxwidth)
413       {
414         endx = maxwidth;
415       }
416
417       g.setColor(Color.black);
418
419       if (av.scaleLeftWrapped)
420       {
421         drawWestScale(g, startRes, endx, ypos);
422       }
423
424       if (av.scaleRightWrapped)
425       {
426         g.translate(canvasWidth - LABEL_EAST, 0);
427         drawEastScale(g, startRes, endx, ypos);
428         g.translate(-(canvasWidth - LABEL_EAST), 0);
429       }
430
431       g.translate(LABEL_WEST, 0);
432
433       if (av.scaleAboveWrapped)
434       {
435         drawNorthScale(g, startRes, endx, ypos);
436       }
437       if (av.hasHiddenColumns && av.showHiddenMarkers)
438       {
439         g.setColor(Color.blue);
440         int res;
441         for (int i = 0; i < av.getColumnSelection().getHiddenColumns()
442                 .size(); i++)
443         {
444           res = av.getColumnSelection().findHiddenRegionPosition(i)
445                   - startRes;
446
447           if (res < 0 || res > endx - startRes)
448           {
449             continue;
450           }
451
452           gg.fillPolygon(new int[]
453           { res * av.charWidth - av.charHeight / 4,
454               res * av.charWidth + av.charHeight / 4, res * av.charWidth },
455                   new int[]
456                   { ypos - (av.charHeight / 2), ypos - (av.charHeight / 2),
457                       ypos - (av.charHeight / 2) + 8 }, 3);
458
459         }
460       }
461
462       if (g.getClip() == null)
463       {
464         g.setClip(0, 0, cWidth * av.charWidth, canvasHeight);
465       }
466
467       drawPanel(g, startRes, endx, 0, al.getHeight(), ypos);
468       g.setClip(null);
469
470       if (av.showAnnotation)
471       {
472         g.translate(0, cHeight + ypos + 4);
473         if (annotations == null)
474         {
475           annotations = new AnnotationPanel(av);
476         }
477
478         annotations.drawComponent(g, startRes, endx + 1);
479         g.translate(0, -cHeight - ypos - 4);
480       }
481       g.translate(-LABEL_WEST, 0);
482
483       ypos += cHeight + getAnnotationHeight() + hgap;
484
485       startRes += cWidth;
486     }
487
488   }
489
490   AnnotationPanel annotations;
491
492   int getAnnotationHeight()
493   {
494     if (!av.showAnnotation)
495     {
496       return 0;
497     }
498
499     if (annotations == null)
500     {
501       annotations = new AnnotationPanel(av);
502     }
503
504     return annotations.adjustPanelHeight();
505   }
506
507   void drawPanel(Graphics g1, int startRes, int endRes, int startSeq,
508           int endSeq, int offset)
509   {
510     if (!av.hasHiddenColumns)
511     {
512       draw(g1, startRes, endRes, startSeq, endSeq, offset);
513     }
514     else
515     {
516       java.util.Vector regions = av.getColumnSelection().getHiddenColumns();
517
518       int screenY = 0;
519       int blockStart = startRes;
520       int blockEnd = endRes;
521
522       for (int i = 0; i < regions.size(); i++)
523       {
524         int[] region = (int[]) regions.elementAt(i);
525         int hideStart = region[0];
526         int hideEnd = region[1];
527
528         if (hideStart <= blockStart)
529         {
530           blockStart += (hideEnd - hideStart) + 1;
531           continue;
532         }
533
534         blockEnd = hideStart - 1;
535
536         g1.translate(screenY * av.charWidth, 0);
537
538         draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
539
540         if (av.getShowHiddenMarkers())
541         {
542           g1.setColor(Color.blue);
543           g1
544                   .drawLine((blockEnd - blockStart + 1) * av.charWidth - 1,
545                           0 + offset, (blockEnd - blockStart + 1)
546                                   * av.charWidth - 1, (endSeq - startSeq)
547                                   * av.charHeight + offset);
548         }
549
550         g1.translate(-screenY * av.charWidth, 0);
551         screenY += blockEnd - blockStart + 1;
552         blockStart = hideEnd + 1;
553       }
554
555       if (screenY <= (endRes - startRes))
556       {
557         blockEnd = blockStart + (endRes - startRes) - screenY;
558         g1.translate(screenY * av.charWidth, 0);
559         draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
560
561         g1.translate(-screenY * av.charWidth, 0);
562       }
563     }
564
565   }
566
567   // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
568   // int x1, int x2, int y1, int y2, int startx, int starty,
569   void draw(Graphics g, int startRes, int endRes, int startSeq, int endSeq,
570           int offset)
571   {
572     g.setFont(av.getFont());
573     sr.prepare(g, av.renderGaps);
574
575     SequenceI nextSeq;
576
577     // / First draw the sequences
578     // ///////////////////////////
579     for (int i = startSeq; i < endSeq; i++)
580     {
581       nextSeq = av.alignment.getSequenceAt(i);
582
583       if (nextSeq == null)
584       {
585         continue;
586       }
587
588       sr.drawSequence(nextSeq, av.alignment.findAllGroups(nextSeq),
589               startRes, endRes, offset + ((i - startSeq) * av.charHeight));
590
591       if (av.showSequenceFeatures)
592       {
593         fr.drawSequence(g, nextSeq, startRes, endRes, offset
594                 + ((i - startSeq) * av.charHeight));
595       }
596
597       // / Highlight search Results once all sequences have been drawn
598       // ////////////////////////////////////////////////////////
599       if (searchResults != null)
600       {
601         int[] visibleResults = searchResults.getResults(nextSeq, startRes,
602                 endRes);
603         if (visibleResults != null)
604         {
605           for (int r = 0; r < visibleResults.length; r += 2)
606           {
607             sr.drawHighlightedText(nextSeq, visibleResults[r],
608                     visibleResults[r + 1], (visibleResults[r] - startRes)
609                             * av.charWidth, offset
610                             + ((i - startSeq) * av.charHeight));
611           }
612         }
613       }
614
615       if (av.cursorMode && cursorY == i && cursorX >= startRes
616               && cursorX <= endRes)
617       {
618         sr.drawCursor(nextSeq, cursorX,
619                 (cursorX - startRes) * av.charWidth, offset
620                         + ((i - startSeq) * av.charHeight));
621       }
622     }
623
624     if (av.getSelectionGroup() != null
625             || av.alignment.getGroups().size() > 0)
626     {
627       drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
628     }
629
630   }
631
632   void drawGroupsBoundaries(Graphics g, int startRes, int endRes,
633           int startSeq, int endSeq, int offset)
634   {
635     //
636     // ///////////////////////////////////
637     // Now outline any areas if necessary
638     // ///////////////////////////////////
639     SequenceGroup group = av.getSelectionGroup();
640
641     int sx = -1;
642     int sy = -1;
643     int ex = -1;
644     int groupIndex = -1;
645
646     if ((group == null) && (av.alignment.getGroups().size() > 0))
647     {
648       group = (SequenceGroup) av.alignment.getGroups().elementAt(0);
649       groupIndex = 0;
650     }
651
652     if (group != null)
653     {
654       do
655       {
656         int oldY = -1;
657         int i = 0;
658         boolean inGroup = false;
659         int top = -1;
660         int bottom = -1;
661         int alHeight = av.alignment.getHeight() - 1;
662
663         for (i = startSeq; i < endSeq; i++)
664         {
665           sx = (group.getStartRes() - startRes) * av.charWidth;
666           sy = offset + ((i - startSeq) * av.charHeight);
667           ex = (((group.getEndRes() + 1) - group.getStartRes()) * av.charWidth) - 1;
668
669           if (sx + ex < 0 || sx > imgWidth)
670           {
671             continue;
672           }
673
674           if ((sx <= (endRes - startRes) * av.charWidth)
675                   && group.getSequences(null).contains(
676                           av.alignment.getSequenceAt(i)))
677           {
678             if ((bottom == -1)
679                     && (i >= alHeight || !group.getSequences(null)
680                             .contains(av.alignment.getSequenceAt(i + 1))))
681             {
682               bottom = sy + av.charHeight;
683             }
684
685             if (!inGroup)
686             {
687               if (((top == -1) && (i == 0))
688                       || !group.getSequences(null).contains(
689                               av.alignment.getSequenceAt(i - 1)))
690               {
691                 top = sy;
692               }
693
694               oldY = sy;
695               inGroup = true;
696
697               if (group == av.getSelectionGroup())
698               {
699                 g.setColor(Color.red);
700               }
701               else
702               {
703                 g.setColor(group.getOutlineColour());
704               }
705             }
706           }
707           else
708           {
709             if (inGroup)
710             {
711               if (sx >= 0 && sx < imgWidth)
712               {
713                 g.drawLine(sx, oldY, sx, sy);
714               }
715
716               if (sx + ex < imgWidth)
717               {
718                 g.drawLine(sx + ex, oldY, sx + ex, sy);
719               }
720
721               if (sx < 0)
722               {
723                 ex += sx;
724                 sx = 0;
725               }
726
727               if (sx + ex > imgWidth)
728               {
729                 ex = imgWidth;
730               }
731
732               else if (sx + ex >= (endRes - startRes + 1) * av.charWidth)
733               {
734                 ex = (endRes - startRes + 1) * av.charWidth;
735               }
736
737               if (top != -1)
738               {
739                 g.drawLine(sx, top, sx + ex, top);
740                 top = -1;
741               }
742
743               if (bottom != -1)
744               {
745                 g.drawLine(sx, bottom, sx + ex, bottom);
746                 bottom = -1;
747               }
748
749               inGroup = false;
750             }
751           }
752         }
753
754         if (inGroup)
755         {
756           sy = offset + ((i - startSeq) * av.charHeight);
757           if (sx >= 0 && sx < imgWidth)
758           {
759             g.drawLine(sx, oldY, sx, sy);
760           }
761
762           if (sx + ex < imgWidth)
763           {
764             g.drawLine(sx + ex, oldY, sx + ex, sy);
765           }
766
767           if (sx < 0)
768           {
769             ex += sx;
770             sx = 0;
771           }
772
773           if (sx + ex > imgWidth)
774           {
775             ex = imgWidth;
776           }
777           else if (sx + ex >= (endRes - startRes + 1) * av.charWidth)
778           {
779             ex = (endRes - startRes + 1) * av.charWidth;
780           }
781
782           if (top != -1)
783           {
784             g.drawLine(sx, top, sx + ex, top);
785             top = -1;
786           }
787
788           if (bottom != -1)
789           {
790             g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
791             bottom = -1;
792           }
793
794           inGroup = false;
795         }
796
797         groupIndex++;
798
799         if (groupIndex >= av.alignment.getGroups().size())
800         {
801           break;
802         }
803
804         group = (SequenceGroup) av.alignment.getGroups().elementAt(
805                 groupIndex);
806       } while (groupIndex < av.alignment.getGroups().size());
807
808     }
809   }
810
811   public void highlightSearchResults(SearchResults results)
812   {
813     searchResults = results;
814
815     repaint();
816   }
817
818 }