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