efe43b8dabae552b6810c3644b5561a1a54843d9
[jalview.git] / src / jalview / appletgui / SeqCanvas.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8)
3  * Copyright (C) 2012 J Procter, AM Waterhouse, LM Lui, J Engelhardt, 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.getAlignment().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.getAlignment().getHeight(); i++)
114     {
115       SequenceI seq = av.getAlignment().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.getAlignment().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.getAlignment().getHeight(); i++)
155     {
156       seq = av.getAlignment().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     AlignmentI alignment = av.getAlignment();
352     for (int i = 0; i < alignment.getHeight(); i++)
353     {
354       tmp = 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.getAlignment().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.drawLine((blockEnd - blockStart + 1) * av.charWidth - 1,
544                   0 + offset, (blockEnd - blockStart + 1) * av.charWidth
545                           - 1, (endSeq - startSeq) * av.charHeight + offset);
546         }
547
548         g1.translate(-screenY * av.charWidth, 0);
549         screenY += blockEnd - blockStart + 1;
550         blockStart = hideEnd + 1;
551       }
552
553       if (screenY <= (endRes - startRes))
554       {
555         blockEnd = blockStart + (endRes - startRes) - screenY;
556         g1.translate(screenY * av.charWidth, 0);
557         draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
558
559         g1.translate(-screenY * av.charWidth, 0);
560       }
561     }
562
563   }
564
565   // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
566   // int x1, int x2, int y1, int y2, int startx, int starty,
567   void draw(Graphics g, int startRes, int endRes, int startSeq, int endSeq,
568           int offset)
569   {
570     g.setFont(av.getFont());
571     sr.prepare(g, av.renderGaps);
572
573     SequenceI nextSeq;
574
575     // / First draw the sequences
576     // ///////////////////////////
577     for (int i = startSeq; i < endSeq; i++)
578     {
579       nextSeq = av.getAlignment().getSequenceAt(i);
580
581       if (nextSeq == null)
582       {
583         continue;
584       }
585
586       sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
587               startRes, endRes, offset + ((i - startSeq) * av.charHeight));
588
589       if (av.showSequenceFeatures)
590       {
591         fr.drawSequence(g, nextSeq, startRes, endRes, offset
592                 + ((i - startSeq) * av.charHeight));
593       }
594
595       // / Highlight search Results once all sequences have been drawn
596       // ////////////////////////////////////////////////////////
597       if (searchResults != null)
598       {
599         int[] visibleResults = searchResults.getResults(nextSeq, startRes,
600                 endRes);
601         if (visibleResults != null)
602         {
603           for (int r = 0; r < visibleResults.length; r += 2)
604           {
605             sr.drawHighlightedText(nextSeq, visibleResults[r],
606                     visibleResults[r + 1], (visibleResults[r] - startRes)
607                             * av.charWidth, offset
608                             + ((i - startSeq) * av.charHeight));
609           }
610         }
611       }
612
613       if (av.cursorMode && cursorY == i && cursorX >= startRes
614               && cursorX <= endRes)
615       {
616         sr.drawCursor(nextSeq, cursorX,
617                 (cursorX - startRes) * av.charWidth, offset
618                         + ((i - startSeq) * av.charHeight));
619       }
620     }
621
622     if (av.getSelectionGroup() != null
623             || av.getAlignment().getGroups().size() > 0)
624     {
625       drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
626     }
627
628   }
629
630   void drawGroupsBoundaries(Graphics g, int startRes, int endRes,
631           int startSeq, int endSeq, int offset)
632   {
633     //
634     // ///////////////////////////////////
635     // Now outline any areas if necessary
636     // ///////////////////////////////////
637     SequenceGroup group = av.getSelectionGroup();
638
639     int sx = -1;
640     int sy = -1;
641     int ex = -1;
642     int groupIndex = -1;
643
644     if ((group == null) && (av.getAlignment().getGroups().size() > 0))
645     {
646       group = (SequenceGroup) av.getAlignment().getGroups().get(0);
647       groupIndex = 0;
648     }
649
650     if (group != null)
651     {
652       do
653       {
654         int oldY = -1;
655         int i = 0;
656         boolean inGroup = false;
657         int top = -1;
658         int bottom = -1;
659         int alHeight = av.getAlignment().getHeight() - 1;
660
661         for (i = startSeq; i < endSeq; i++)
662         {
663           sx = (group.getStartRes() - startRes) * av.charWidth;
664           sy = offset + ((i - startSeq) * av.charHeight);
665           ex = (((group.getEndRes() + 1) - group.getStartRes()) * av.charWidth) - 1;
666
667           if (sx + ex < 0 || sx > imgWidth)
668           {
669             continue;
670           }
671
672           if ((sx <= (endRes - startRes) * av.charWidth)
673                   && group.getSequences(null).contains(
674                           av.getAlignment().getSequenceAt(i)))
675           {
676             if ((bottom == -1)
677                     && (i >= alHeight || !group.getSequences(null)
678                             .contains(
679                                     av.getAlignment().getSequenceAt(i + 1))))
680             {
681               bottom = sy + av.charHeight;
682             }
683
684             if (!inGroup)
685             {
686               if (((top == -1) && (i == 0))
687                       || !group.getSequences(null).contains(
688                               av.getAlignment().getSequenceAt(i - 1)))
689               {
690                 top = sy;
691               }
692
693               oldY = sy;
694               inGroup = true;
695
696               if (group == av.getSelectionGroup())
697               {
698                 g.setColor(Color.red);
699               }
700               else
701               {
702                 g.setColor(group.getOutlineColour());
703               }
704             }
705           }
706           else
707           {
708             if (inGroup)
709             {
710               if (sx >= 0 && sx < imgWidth)
711               {
712                 g.drawLine(sx, oldY, sx, sy);
713               }
714
715               if (sx + ex < imgWidth)
716               {
717                 g.drawLine(sx + ex, oldY, sx + ex, sy);
718               }
719
720               if (sx < 0)
721               {
722                 ex += sx;
723                 sx = 0;
724               }
725
726               if (sx + ex > imgWidth)
727               {
728                 ex = imgWidth;
729               }
730
731               else if (sx + ex >= (endRes - startRes + 1) * av.charWidth)
732               {
733                 ex = (endRes - startRes + 1) * av.charWidth;
734               }
735
736               if (top != -1)
737               {
738                 g.drawLine(sx, top, sx + ex, top);
739                 top = -1;
740               }
741
742               if (bottom != -1)
743               {
744                 g.drawLine(sx, bottom, sx + ex, bottom);
745                 bottom = -1;
746               }
747
748               inGroup = false;
749             }
750           }
751         }
752
753         if (inGroup)
754         {
755           sy = offset + ((i - startSeq) * av.charHeight);
756           if (sx >= 0 && sx < imgWidth)
757           {
758             g.drawLine(sx, oldY, sx, sy);
759           }
760
761           if (sx + ex < imgWidth)
762           {
763             g.drawLine(sx + ex, oldY, sx + ex, sy);
764           }
765
766           if (sx < 0)
767           {
768             ex += sx;
769             sx = 0;
770           }
771
772           if (sx + ex > imgWidth)
773           {
774             ex = imgWidth;
775           }
776           else if (sx + ex >= (endRes - startRes + 1) * av.charWidth)
777           {
778             ex = (endRes - startRes + 1) * av.charWidth;
779           }
780
781           if (top != -1)
782           {
783             g.drawLine(sx, top, sx + ex, top);
784             top = -1;
785           }
786
787           if (bottom != -1)
788           {
789             g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
790             bottom = -1;
791           }
792
793           inGroup = false;
794         }
795
796         groupIndex++;
797
798         if (groupIndex >= av.getAlignment().getGroups().size())
799         {
800           break;
801         }
802
803         group = (SequenceGroup) av.getAlignment().getGroups()
804                 .get(groupIndex);
805       } while (groupIndex < av.getAlignment().getGroups().size());
806
807     }
808   }
809
810   public void highlightSearchResults(SearchResults results)
811   {
812     searchResults = results;
813
814     repaint();
815   }
816
817 }