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