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