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