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