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