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