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