JAL-1807 - Bob's last(?) before leaving Dundee -- adds fast file loading
[jalviewjs.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 awt2swing.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       awt2swing.Util.drawString(g, 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         awt2swing.Util.drawString(g, 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         awt2swing.Util.drawString(g, 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
281   @Override
282   public void paintComponent(Graphics g)
283   {
284     if (img != null
285             && (fastPaint || (getSize().width != g.getClipBounds().width) || (getSize().height != g
286                     .getClipBounds().height)))
287     {
288       g.drawImage(img, 0, 0, this);
289       fastPaint = false;
290       return;
291     }
292
293     if (fastPaint)
294     {
295       g.drawImage(img, 0, 0, this);
296       fastPaint = false;
297       return;
298     }
299
300     updateViewport();
301     // this draws the whole of the alignment
302     imgWidth = this.getSize().width;
303     imgHeight = this.getSize().height;
304
305     imgWidth -= imgWidth % avcharWidth;
306     imgHeight -= imgHeight % avcharHeight;
307
308     if (imgWidth < 1 || imgHeight < 1)
309     {
310       return;
311     }
312
313     if (img == null || imgWidth != img.getWidth(this)
314             || imgHeight != img.getHeight(this))
315     {
316       img = createImage(imgWidth, imgHeight);
317       gg = img.getGraphics();
318       gg.setFont(av.getFont());
319     }
320
321     gg.setColor(Color.white);
322     gg.fillRect(0, 0, imgWidth, imgHeight);
323
324     if (av.getWrapAlignment())
325     {
326       drawWrappedPanel(gg, imgWidth, imgHeight, av.startRes);
327     }
328     else
329     {
330       drawPanel(gg, av.startRes, av.endRes, av.startSeq, av.endSeq, 0);
331     }
332
333     g.drawImage(img, 0, 0, this);
334
335   }
336
337   int LABEL_WEST, LABEL_EAST;
338
339   public int getWrappedCanvasWidth(int cwidth)
340   {
341     cwidth -= cwidth % av.getCharWidth();
342
343     FontMetrics fm = getFontMetrics(av.getFont());
344
345     LABEL_EAST = 0;
346     LABEL_WEST = 0;
347
348     if (av.getScaleRightWrapped())
349     {
350       LABEL_EAST = fm.stringWidth(getMask());
351     }
352
353     if (av.getScaleLeftWrapped())
354     {
355       LABEL_WEST = fm.stringWidth(getMask());
356     }
357
358     return (cwidth - LABEL_EAST - LABEL_WEST) / av.getCharWidth();
359   }
360
361   /**
362    * Generates a string of zeroes.
363    * 
364    * @return String
365    */
366   String getMask()
367   {
368     String mask = "0";
369     int maxWidth = 0;
370     int tmp;
371     AlignmentI alignment = av.getAlignment();
372     for (int i = 0; i < alignment.getHeight(); i++)
373     {
374       tmp = alignment.getSequenceAt(i).getEnd();
375       if (tmp > maxWidth)
376       {
377         maxWidth = tmp;
378       }
379     }
380
381     for (int i = maxWidth; i > 0; i /= 10)
382     {
383       mask += "0";
384     }
385     return mask;
386   }
387
388   private void drawWrappedPanel(Graphics g, int canvasWidth,
389           int canvasHeight, int startRes)
390   {
391     AlignmentI al = av.getAlignment();
392
393     FontMetrics fm = getFontMetrics(av.getFont());
394
395     if (av.getScaleRightWrapped())
396     {
397       LABEL_EAST = fm.stringWidth(getMask());
398     }
399
400     if (av.getScaleLeftWrapped())
401     {
402       LABEL_WEST = fm.stringWidth(getMask());
403     }
404
405     int hgap = avcharHeight;
406     if (av.getScaleAboveWrapped())
407     {
408       hgap += avcharHeight;
409     }
410
411     int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / avcharWidth;
412     int cHeight = av.getAlignment().getHeight() * avcharHeight;
413
414     av.setWrappedWidth(cWidth);
415
416     av.endRes = av.startRes + cWidth;
417
418     int endx;
419     int ypos = hgap;
420
421     int maxwidth = av.getAlignment().getWidth() - 1;
422
423     if (av.hasHiddenColumns())
424     {
425       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
426     }
427
428     while ((ypos <= canvasHeight) && (startRes < maxwidth))
429     {
430       endx = startRes + cWidth - 1;
431
432       if (endx > maxwidth)
433       {
434         endx = maxwidth;
435       }
436
437       g.setColor(Color.black);
438
439       if (av.getScaleLeftWrapped())
440       {
441         drawWestScale(g, startRes, endx, ypos);
442       }
443
444       if (av.getScaleRightWrapped())
445       {
446         g.translate(canvasWidth - LABEL_EAST, 0);
447         drawEastScale(g, startRes, endx, ypos);
448         g.translate(-(canvasWidth - LABEL_EAST), 0);
449       }
450
451       g.translate(LABEL_WEST, 0);
452
453       if (av.getScaleAboveWrapped())
454       {
455         drawNorthScale(g, startRes, endx, ypos);
456       }
457       if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
458       {
459         g.setColor(Color.blue);
460         int res;
461         for (int i = 0; i < av.getColumnSelection().getHiddenColumns()
462                 .size(); i++)
463         {
464           res = av.getColumnSelection().findHiddenRegionPosition(i)
465                   - startRes;
466
467           if (res < 0 || res > endx - startRes)
468           {
469             continue;
470           }
471
472           gg.fillPolygon(new int[]
473           { res * avcharWidth - avcharHeight / 4,
474               res * avcharWidth + avcharHeight / 4, res * avcharWidth },
475                   new int[]
476                   { ypos - (avcharHeight / 2), ypos - (avcharHeight / 2),
477                       ypos - (avcharHeight / 2) + 8 }, 3);
478
479         }
480       }
481
482       if (g.getClip() == null)
483       {
484         g.setClip(0, 0, cWidth * avcharWidth, canvasHeight);
485       }
486
487       drawPanel(g, startRes, endx, 0, al.getHeight(), ypos);
488       g.setClip(null);
489
490       if (av.isShowAnnotation())
491       {
492         g.translate(0, cHeight + ypos + 4);
493         if (annotations == null)
494         {
495           annotations = new AnnotationPanel(av);
496         }
497
498         annotations.drawComponent(g, startRes, endx + 1);
499         g.translate(0, -cHeight - ypos - 4);
500       }
501       g.translate(-LABEL_WEST, 0);
502
503       ypos += cHeight + getAnnotationHeight() + hgap;
504
505       startRes += cWidth;
506     }
507
508   }
509
510   AnnotationPanel annotations;
511
512   int getAnnotationHeight()
513   {
514     if (!av.isShowAnnotation())
515     {
516       return 0;
517     }
518
519     if (annotations == null)
520     {
521       annotations = new AnnotationPanel(av);
522     }
523
524     return annotations.adjustPanelHeight();
525   }
526
527   private void drawPanel(Graphics g1, int startRes, int endRes,
528           int startSeq,
529           int endSeq, int offset)
530   {
531
532
533     if (!av.hasHiddenColumns())
534     {
535       draw(g1, startRes, endRes, startSeq, endSeq, offset);
536     }
537     else
538     {
539
540       int screenY = 0;
541       int blockStart = startRes;
542       int blockEnd = endRes;
543
544       if (av.hasHiddenColumns())
545       {
546         for (int[] region : av.getColumnSelection().getHiddenColumns())
547         {
548           int hideStart = region[0];
549           int hideEnd = region[1];
550
551           if (hideStart <= blockStart)
552           {
553             blockStart += (hideEnd - hideStart) + 1;
554             continue;
555           }
556
557           blockEnd = hideStart - 1;
558
559           g1.translate(screenY * avcharWidth, 0);
560
561           draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
562
563           if (av.getShowHiddenMarkers())
564           {
565             g1.setColor(Color.blue);
566             g1.drawLine((blockEnd - blockStart + 1) * avcharWidth - 1,
567                     0 + offset, (blockEnd - blockStart + 1) * avcharWidth
568                             - 1, (endSeq - startSeq) * avcharHeight
569                             + offset);
570           }
571
572           g1.translate(-screenY * avcharWidth, 0);
573           screenY += blockEnd - blockStart + 1;
574           blockStart = hideEnd + 1;
575         }
576       }
577       if (screenY <= (endRes - startRes))
578       {
579         blockEnd = blockStart + (endRes - startRes) - screenY;
580         g1.translate(screenY * avcharWidth, 0);
581         draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
582
583         g1.translate(-screenY * avcharWidth, 0);
584       }
585     }
586
587   }
588
589   // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
590   // int x1, int x2, int y1, int y2, int startx, int starty,
591   void draw(Graphics g, int startRes, int endRes, int startSeq, int endSeq,
592           int offset)
593   {
594     g.setFont(av.getFont());
595     sr.prepare(g, av.isRenderGaps());
596     updateViewport();
597     SequenceI nextSeq;
598
599     // / First draw the sequences
600     // ///////////////////////////
601     for (int i = startSeq; i < endSeq; i++)
602     {
603       nextSeq = av.getAlignment().getSequenceAt(i);
604
605       if (nextSeq == null)
606       {
607         continue;
608       }
609
610       sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
611               startRes, endRes, offset + ((i - startSeq) * avcharHeight));
612
613       if (av.isShowSequenceFeatures())
614       {
615         fr.drawSequence(g, nextSeq, startRes, endRes, offset
616                 + ((i - startSeq) * avcharHeight));
617       }
618
619       // / Highlight search Results once all sequences have been drawn
620       // ////////////////////////////////////////////////////////
621       if (searchResults != null)
622       {
623         int[] visibleResults = searchResults.getResults(nextSeq, startRes,
624                 endRes);
625         if (visibleResults != null)
626         {
627           for (int r = 0; r < visibleResults.length; r += 2)
628           {
629             sr.drawHighlightedText(nextSeq, visibleResults[r],
630                     visibleResults[r + 1], (visibleResults[r] - startRes)
631                             * avcharWidth, offset
632                             + ((i - startSeq) * avcharHeight));
633           }
634         }
635       }
636
637       if (av.cursorMode && cursorY == i && cursorX >= startRes
638               && cursorX <= endRes)
639       {
640         sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * avcharWidth,
641                 offset + ((i - startSeq) * avcharHeight));
642       }
643     }
644
645     if (av.getSelectionGroup() != null
646             || av.getAlignment().getGroups().size() > 0)
647     {
648       drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
649     }
650
651   }
652
653   private void drawGroupsBoundaries(Graphics g, int startRes, int endRes,
654           int startSeq, int endSeq, int offset)
655   {
656     //
657     // ///////////////////////////////////
658     // Now outline any areas if necessary
659     // ///////////////////////////////////
660     SequenceGroup group = av.getSelectionGroup();
661
662     int sx = -1;
663     int sy = -1;
664     int ex = -1;
665     int groupIndex = -1;
666
667     if ((group == null) && (av.getAlignment().getGroups().size() > 0))
668     {
669       group = av.getAlignment().getGroups().get(0);
670       groupIndex = 0;
671     }
672
673     if (group != null)
674     {
675       do
676       {
677         int oldY = -1;
678         int i = 0;
679         boolean inGroup = false;
680         int top = -1;
681         int bottom = -1;
682         int alHeight = av.getAlignment().getHeight() - 1;
683
684         for (i = startSeq; i < endSeq; i++)
685         {
686           sx = (group.getStartRes() - startRes) * avcharWidth;
687           sy = offset + ((i - startSeq) * avcharHeight);
688           ex = (((group.getEndRes() + 1) - group.getStartRes()) * avcharWidth) - 1;
689
690           if (sx + ex < 0 || sx > imgWidth)
691           {
692             continue;
693           }
694
695           if ((sx <= (endRes - startRes) * avcharWidth)
696                   && group.getSequences(null).contains(
697                           av.getAlignment().getSequenceAt(i)))
698           {
699             if ((bottom == -1)
700                     && (i >= alHeight || !group.getSequences(null)
701                             .contains(
702                                     av.getAlignment().getSequenceAt(i + 1))))
703             {
704               bottom = sy + avcharHeight;
705             }
706
707             if (!inGroup)
708             {
709               if (((top == -1) && (i == 0))
710                       || !group.getSequences(null).contains(
711                               av.getAlignment().getSequenceAt(i - 1)))
712               {
713                 top = sy;
714               }
715
716               oldY = sy;
717               inGroup = true;
718
719               if (group == av.getSelectionGroup())
720               {
721                 g.setColor(Color.red);
722               }
723               else
724               {
725                 g.setColor(group.getOutlineColour());
726               }
727             }
728           }
729           else
730           {
731             if (inGroup)
732             {
733               if (sx >= 0 && sx < imgWidth)
734               {
735                 g.drawLine(sx, oldY, sx, sy);
736               }
737
738               if (sx + ex < imgWidth)
739               {
740                 g.drawLine(sx + ex, oldY, sx + ex, sy);
741               }
742
743               if (sx < 0)
744               {
745                 ex += sx;
746                 sx = 0;
747               }
748
749               if (sx + ex > imgWidth)
750               {
751                 ex = imgWidth;
752               }
753
754               else if (sx + ex >= (endRes - startRes + 1) * avcharWidth)
755               {
756                 ex = (endRes - startRes + 1) * avcharWidth;
757               }
758
759               if (top != -1)
760               {
761                 g.drawLine(sx, top, sx + ex, top);
762                 top = -1;
763               }
764
765               if (bottom != -1)
766               {
767                 g.drawLine(sx, bottom, sx + ex, bottom);
768                 bottom = -1;
769               }
770
771               inGroup = false;
772             }
773           }
774         }
775
776         if (inGroup)
777         {
778           sy = offset + ((i - startSeq) * avcharHeight);
779           if (sx >= 0 && sx < imgWidth)
780           {
781             g.drawLine(sx, oldY, sx, sy);
782           }
783
784           if (sx + ex < imgWidth)
785           {
786             g.drawLine(sx + ex, oldY, sx + ex, sy);
787           }
788
789           if (sx < 0)
790           {
791             ex += sx;
792             sx = 0;
793           }
794
795           if (sx + ex > imgWidth)
796           {
797             ex = imgWidth;
798           }
799           else if (sx + ex >= (endRes - startRes + 1) * avcharWidth)
800           {
801             ex = (endRes - startRes + 1) * avcharWidth;
802           }
803
804           if (top != -1)
805           {
806             g.drawLine(sx, top, sx + ex, top);
807             top = -1;
808           }
809
810           if (bottom != -1)
811           {
812             g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
813             bottom = -1;
814           }
815
816           inGroup = false;
817         }
818
819         groupIndex++;
820
821         if (groupIndex >= av.getAlignment().getGroups().size())
822         {
823           break;
824         }
825
826         group = av.getAlignment().getGroups()
827                 .get(groupIndex);
828       } while (groupIndex < av.getAlignment().getGroups().size());
829
830     }
831   }
832
833   public void highlightSearchResults(SearchResults results)
834   {
835     searchResults = results;
836
837     repaint();
838   }
839
840 }