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