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