JAL-1807 update
[jalviewjs.git] / unused / 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.Comparison;
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
35 import javax.swing.JPanel;
36
37 public class SeqCanvas extends JPanel
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   SearchResults searchResults = null;
54
55   boolean fastPaint = false;
56
57   int cursorX = 0;
58
59   int cursorY = 0;
60
61   public SeqCanvas(AlignViewport av)
62   {
63     this.av = av;
64     fr = new FeatureRenderer(av);
65     sr = new SequenceRenderer(av);
66     PaintRefresher.Register(this, av.getSequenceSetId());
67     updateViewport();
68   }
69
70   int avcharHeight = 0, avcharWidth = 0;
71
72   private void updateViewport()
73   {
74     avcharHeight = av.getCharHeight();
75     avcharWidth = av.getCharWidth();
76   }
77
78   public AlignmentViewport getViewport()
79   {
80     return av;
81   }
82
83   public FeatureRenderer getFeatureRenderer()
84   {
85     return fr;
86   }
87
88   public SequenceRenderer getSequenceRenderer()
89   {
90     return sr;
91   }
92
93   private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
94   {
95     int scalestartx = startx - startx % 10 + 10;
96
97     g.setColor(Color.black);
98
99     // NORTH SCALE
100     for (int i = scalestartx; i < endx; i += 10)
101     {
102       int value = i;
103       if (av.hasHiddenColumns())
104       {
105         value = av.getColumnSelection().adjustForHiddenColumns(value);
106       }
107
108       g.drawString(String.valueOf(value), (i - startx - 1) * avcharWidth,
109               ypos - (avcharHeight / 2));
110
111       g.drawLine(((i - startx - 1) * avcharWidth) + (avcharWidth / 2),
112               (ypos + 2) - (avcharHeight / 2),
113               ((i - startx - 1) * avcharWidth) + (avcharWidth / 2),
114               ypos - 2);
115     }
116   }
117
118   private void drawWestScale(Graphics g, int startx, int endx, int ypos)
119   {
120     FontMetrics fm = getFontMetrics(av.getFont());
121     ypos += avcharHeight;
122     if (av.hasHiddenColumns())
123     {
124       startx = av.getColumnSelection().adjustForHiddenColumns(startx);
125       endx = av.getColumnSelection().adjustForHiddenColumns(endx);
126     }
127
128     int maxwidth = av.getAlignment().getWidth();
129     if (av.hasHiddenColumns())
130     {
131       maxwidth = av.getColumnSelection().findColumnPosition(maxwidth) - 1;
132     }
133
134     // WEST SCALE
135     for (int i = 0; i < av.getAlignment().getHeight(); i++)
136     {
137       SequenceI seq = av.getAlignment().getSequenceAt(i);
138       int index = startx;
139       int value = -1;
140
141       while (index < endx)
142       {
143         if (Comparison.isGap(seq.getCharAt(index)))
144         {
145           index++;
146
147           continue;
148         }
149
150         value = av.getAlignment().getSequenceAt(i).findPosition(index);
151
152         break;
153       }
154
155       if (value != -1)
156       {
157         int x = LABEL_WEST - fm.stringWidth(String.valueOf(value))
158                 - avcharWidth / 2;
159         g.drawString(value + "", x, (ypos + (i * avcharHeight))
160                 - (avcharHeight / 5));
161       }
162     }
163   }
164
165   private void drawEastScale(Graphics g, int startx, int endx, int ypos)
166   {
167     ypos += avcharHeight;
168
169     if (av.hasHiddenColumns())
170     {
171       endx = av.getColumnSelection().adjustForHiddenColumns(endx);
172     }
173
174     SequenceI seq;
175     // EAST SCALE
176     for (int i = 0; i < av.getAlignment().getHeight(); i++)
177     {
178       seq = av.getAlignment().getSequenceAt(i);
179       int index = endx;
180       int value = -1;
181
182       while (index > startx)
183       {
184         if (Comparison.isGap(seq.getCharAt(index)))
185         {
186           index--;
187
188           continue;
189         }
190
191         value = seq.findPosition(index);
192
193         break;
194       }
195
196       if (value != -1)
197       {
198         g.drawString(String.valueOf(value), 0, (ypos + (i * avcharHeight))
199                 - (avcharHeight / 5));
200       }
201     }
202   }
203
204   int lastsr = 0;
205
206   void fastPaint(int horizontal, int vertical)
207   {
208     if (fastPaint || gg == null)
209     {
210       return;
211     }
212
213     updateViewport();
214
215     // Its possible on certain browsers that the call to fastpaint
216     // is faster than it can paint, so this check here catches
217     // this possibility
218     if (lastsr + horizontal != av.startRes)
219     {
220       horizontal = av.startRes - lastsr;
221     }
222
223     lastsr = av.startRes;
224
225     fastPaint = true;
226     gg.copyArea(horizontal * avcharWidth, vertical * avcharHeight, imgWidth
227             - horizontal * avcharWidth,
228             imgHeight - vertical * avcharHeight, -horizontal * avcharWidth,
229             -vertical * avcharHeight);
230
231     int sr = av.startRes, er = av.endRes, ss = av.startSeq, es = av.endSeq, transX = 0, transY = 0;
232
233     if (horizontal > 0) // scrollbar pulled right, image to the left
234     {
235       transX = (er - sr - horizontal) * avcharWidth;
236       sr = er - horizontal;
237     }
238     else if (horizontal < 0)
239     {
240       er = sr - horizontal;
241     }
242
243     else if (vertical > 0) // scroll down
244     {
245       ss = es - vertical;
246       if (ss < av.startSeq) // ie scrolling too fast, more than a page at a time
247       {
248         ss = av.startSeq;
249       }
250       else
251       {
252         transY = imgHeight - vertical * avcharHeight;
253       }
254     }
255     else if (vertical < 0)
256     {
257       es = ss - vertical;
258       if (es > av.endSeq)
259       {
260         es = av.endSeq;
261       }
262     }
263
264     gg.translate(transX, transY);
265
266     drawPanel(gg, sr, er, ss, es, 0);
267     gg.translate(-transX, -transY);
268
269     repaint();
270
271   }
272
273   /**
274    * Definitions of startx and endx (hopefully): SMJS This is what I'm working
275    * towards! startx is the first residue (starting at 0) to display. endx is
276    * the last residue to display (starting at 0). starty is the first sequence
277    * to display (starting at 0). endy is the last sequence to display (starting
278    * at 0). NOTE 1: The av limits are set in setFont in this class and in the
279    * adjustment listener in SeqPanel when the scrollbars move.
280    */
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[]
479           { res * avcharWidth - avcharHeight / 4,
480               res * avcharWidth + avcharHeight / 4, res * avcharWidth },
481                   new int[]
482                   { ypos - (avcharHeight / 2), ypos - (avcharHeight / 2),
483                       ypos - (avcharHeight / 2) + 8 }, 3);
484
485         }
486       }
487
488       if (g.getClip() == null)
489       {
490         g.setClip(0, 0, cWidth * avcharWidth, canvasHeight);
491       }
492
493       drawPanel(g, startRes, endx, 0, al.getHeight(), ypos);
494       g.setClip(null);
495
496       if (av.isShowAnnotation())
497       {
498         g.translate(0, cHeight + ypos + 4);
499         if (annotations == null)
500         {
501           annotations = new AnnotationPanel(av);
502         }
503
504         annotations.drawComponent(g, startRes, endx + 1);
505         g.translate(0, -cHeight - ypos - 4);
506       }
507       g.translate(-LABEL_WEST, 0);
508
509       ypos += cHeight + getAnnotationHeight() + hgap;
510
511       startRes += cWidth;
512     }
513
514   }
515
516   AnnotationPanel annotations;
517
518   int getAnnotationHeight()
519   {
520     if (!av.isShowAnnotation())
521     {
522       return 0;
523     }
524
525     if (annotations == null)
526     {
527       annotations = new AnnotationPanel(av);
528     }
529
530     return annotations.adjustPanelHeight();
531   }
532
533   private void drawPanel(Graphics g1, int startRes, int endRes,
534           int startSeq,
535           int endSeq, int offset)
536   {
537
538
539     if (!av.hasHiddenColumns())
540     {
541       draw(g1, startRes, endRes, startSeq, endSeq, offset);
542     }
543     else
544     {
545
546       int screenY = 0;
547       int blockStart = startRes;
548       int blockEnd = endRes;
549
550       if (av.hasHiddenColumns())
551       {
552         for (int[] region : av.getColumnSelection().getHiddenColumns())
553         {
554           int hideStart = region[0];
555           int hideEnd = region[1];
556
557           if (hideStart <= blockStart)
558           {
559             blockStart += (hideEnd - hideStart) + 1;
560             continue;
561           }
562
563           blockEnd = hideStart - 1;
564
565           g1.translate(screenY * avcharWidth, 0);
566
567           draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
568
569           if (av.getShowHiddenMarkers())
570           {
571             g1.setColor(Color.blue);
572             g1.drawLine((blockEnd - blockStart + 1) * avcharWidth - 1,
573                     0 + offset, (blockEnd - blockStart + 1) * avcharWidth
574                             - 1, (endSeq - startSeq) * avcharHeight
575                             + offset);
576           }
577
578           g1.translate(-screenY * avcharWidth, 0);
579           screenY += blockEnd - blockStart + 1;
580           blockStart = hideEnd + 1;
581         }
582       }
583       if (screenY <= (endRes - startRes))
584       {
585         blockEnd = blockStart + (endRes - startRes) - screenY;
586         g1.translate(screenY * avcharWidth, 0);
587         draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
588
589         g1.translate(-screenY * avcharWidth, 0);
590       }
591     }
592
593   }
594
595   // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
596   // int x1, int x2, int y1, int y2, int startx, int starty,
597   void draw(Graphics g, int startRes, int endRes, int startSeq, int endSeq,
598           int offset)
599   {
600     g.setFont(av.getFont());
601     sr.prepare(g, av.isRenderGaps());
602     updateViewport();
603     SequenceI nextSeq;
604
605     // / First draw the sequences
606     // ///////////////////////////
607     for (int i = startSeq; i < endSeq; i++)
608     {
609       nextSeq = av.getAlignment().getSequenceAt(i);
610
611       if (nextSeq == null)
612       {
613         continue;
614       }
615
616       sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
617               startRes, endRes, offset + ((i - startSeq) * avcharHeight));
618
619       if (av.isShowSequenceFeatures())
620       {
621         fr.drawSequence(g, nextSeq, startRes, endRes, offset
622                 + ((i - startSeq) * avcharHeight));
623       }
624
625       // / Highlight search Results once all sequences have been drawn
626       // ////////////////////////////////////////////////////////
627       if (searchResults != null)
628       {
629         int[] visibleResults = searchResults.getResults(nextSeq, startRes,
630                 endRes);
631         if (visibleResults != null)
632         {
633           for (int r = 0; r < visibleResults.length; r += 2)
634           {
635             sr.drawHighlightedText(nextSeq, visibleResults[r],
636                     visibleResults[r + 1], (visibleResults[r] - startRes)
637                             * avcharWidth, offset
638                             + ((i - startSeq) * avcharHeight));
639           }
640         }
641       }
642
643       if (av.cursorMode && cursorY == i && cursorX >= startRes
644               && cursorX <= endRes)
645       {
646         sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * avcharWidth,
647                 offset + ((i - startSeq) * avcharHeight));
648       }
649     }
650
651     if (av.getSelectionGroup() != null
652             || av.getAlignment().getGroups().size() > 0)
653     {
654       drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
655     }
656
657   }
658
659   private void drawGroupsBoundaries(Graphics g, int startRes, int endRes,
660           int startSeq, int endSeq, int offset)
661   {
662     //
663     // ///////////////////////////////////
664     // Now outline any areas if necessary
665     // ///////////////////////////////////
666     SequenceGroup group = av.getSelectionGroup();
667
668     int sx = -1;
669     int sy = -1;
670     int ex = -1;
671     int groupIndex = -1;
672
673     if ((group == null) && (av.getAlignment().getGroups().size() > 0))
674     {
675       group = av.getAlignment().getGroups().get(0);
676       groupIndex = 0;
677     }
678
679     if (group != null)
680     {
681       do
682       {
683         int oldY = -1;
684         int i = 0;
685         boolean inGroup = false;
686         int top = -1;
687         int bottom = -1;
688         int alHeight = av.getAlignment().getHeight() - 1;
689
690         for (i = startSeq; i < endSeq; i++)
691         {
692           sx = (group.getStartRes() - startRes) * avcharWidth;
693           sy = offset + ((i - startSeq) * avcharHeight);
694           ex = (((group.getEndRes() + 1) - group.getStartRes()) * avcharWidth) - 1;
695
696           if (sx + ex < 0 || sx > imgWidth)
697           {
698             continue;
699           }
700
701           if ((sx <= (endRes - startRes) * avcharWidth)
702                   && group.getSequences(null).contains(
703                           av.getAlignment().getSequenceAt(i)))
704           {
705             if ((bottom == -1)
706                     && (i >= alHeight || !group.getSequences(null)
707                             .contains(
708                                     av.getAlignment().getSequenceAt(i + 1))))
709             {
710               bottom = sy + avcharHeight;
711             }
712
713             if (!inGroup)
714             {
715               if (((top == -1) && (i == 0))
716                       || !group.getSequences(null).contains(
717                               av.getAlignment().getSequenceAt(i - 1)))
718               {
719                 top = sy;
720               }
721
722               oldY = sy;
723               inGroup = true;
724
725               if (group == av.getSelectionGroup())
726               {
727                 g.setColor(Color.red);
728               }
729               else
730               {
731                 g.setColor(group.getOutlineColour());
732               }
733             }
734           }
735           else
736           {
737             if (inGroup)
738             {
739               if (sx >= 0 && sx < imgWidth)
740               {
741                 g.drawLine(sx, oldY, sx, sy);
742               }
743
744               if (sx + ex < imgWidth)
745               {
746                 g.drawLine(sx + ex, oldY, sx + ex, sy);
747               }
748
749               if (sx < 0)
750               {
751                 ex += sx;
752                 sx = 0;
753               }
754
755               if (sx + ex > imgWidth)
756               {
757                 ex = imgWidth;
758               }
759
760               else if (sx + ex >= (endRes - startRes + 1) * avcharWidth)
761               {
762                 ex = (endRes - startRes + 1) * avcharWidth;
763               }
764
765               if (top != -1)
766               {
767                 g.drawLine(sx, top, sx + ex, top);
768                 top = -1;
769               }
770
771               if (bottom != -1)
772               {
773                 g.drawLine(sx, bottom, sx + ex, bottom);
774                 bottom = -1;
775               }
776
777               inGroup = false;
778             }
779           }
780         }
781
782         if (inGroup)
783         {
784           sy = offset + ((i - startSeq) * avcharHeight);
785           if (sx >= 0 && sx < imgWidth)
786           {
787             g.drawLine(sx, oldY, sx, sy);
788           }
789
790           if (sx + ex < imgWidth)
791           {
792             g.drawLine(sx + ex, oldY, sx + ex, sy);
793           }
794
795           if (sx < 0)
796           {
797             ex += sx;
798             sx = 0;
799           }
800
801           if (sx + ex > imgWidth)
802           {
803             ex = imgWidth;
804           }
805           else if (sx + ex >= (endRes - startRes + 1) * avcharWidth)
806           {
807             ex = (endRes - startRes + 1) * avcharWidth;
808           }
809
810           if (top != -1)
811           {
812             g.drawLine(sx, top, sx + ex, top);
813             top = -1;
814           }
815
816           if (bottom != -1)
817           {
818             g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
819             bottom = -1;
820           }
821
822           inGroup = false;
823         }
824
825         groupIndex++;
826
827         if (groupIndex >= av.getAlignment().getGroups().size())
828         {
829           break;
830         }
831
832         group = av.getAlignment().getGroups()
833                 .get(groupIndex);
834       } while (groupIndex < av.getAlignment().getGroups().size());
835
836     }
837   }
838
839   public void highlightSearchResults(SearchResults results)
840   {
841     searchResults = results;
842
843     repaint();
844   }
845
846 }