JAL-3099 use Alignment.getVisibleWidth() where wanted
[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.HiddenColumns;
25 import jalview.datamodel.SearchResultsI;
26 import jalview.datamodel.SequenceGroup;
27 import jalview.datamodel.SequenceI;
28 import jalview.datamodel.VisibleContigsIterator;
29 import jalview.renderer.ScaleRenderer;
30 import jalview.renderer.ScaleRenderer.ScaleMark;
31 import jalview.viewmodel.AlignmentViewport;
32 import jalview.viewmodel.ViewportListenerI;
33 import jalview.viewmodel.ViewportRanges;
34
35 import java.awt.Color;
36 import java.awt.FontMetrics;
37 import java.awt.Graphics;
38 import java.awt.Image;
39 import java.awt.Panel;
40 import java.beans.PropertyChangeEvent;
41 import java.util.Iterator;
42
43 public class SeqCanvas extends Panel implements ViewportListenerI
44 {
45   FeatureRenderer fr;
46
47   SequenceRenderer sr;
48
49   Image img;
50
51   Graphics gg;
52
53   int imgWidth;
54
55   int imgHeight;
56
57   AlignViewport av;
58
59   boolean fastPaint = false;
60
61   int cursorX = 0;
62
63   int cursorY = 0;
64
65   public SeqCanvas(AlignViewport av)
66   {
67     this.av = av;
68     fr = new FeatureRenderer(av);
69     sr = new SequenceRenderer(av);
70     PaintRefresher.Register(this, av.getSequenceSetId());
71     updateViewport();
72
73     av.getRanges().addPropertyChangeListener(this);
74   }
75
76   int avcharHeight = 0, avcharWidth = 0;
77
78   private void updateViewport()
79   {
80     avcharHeight = av.getCharHeight();
81     avcharWidth = av.getCharWidth();
82   }
83
84   public AlignmentViewport getViewport()
85   {
86     return av;
87   }
88
89   public FeatureRenderer getFeatureRenderer()
90   {
91     return fr;
92   }
93
94   public SequenceRenderer getSequenceRenderer()
95   {
96     return sr;
97   }
98
99   private void drawNorthScale(Graphics g, int startx, int endx, int ypos)
100   {
101     updateViewport();
102     g.setColor(Color.black);
103     for (ScaleMark mark : new ScaleRenderer().calculateMarks(av, startx,
104             endx))
105     {
106       int mpos = mark.column; // (i - startx - 1)
107       if (mpos < 0)
108       {
109         continue;
110       }
111       String mstring = mark.text;
112
113       if (mark.major)
114       {
115         if (mstring != null)
116         {
117           g.drawString(mstring, mpos * avcharWidth,
118                   ypos - (avcharHeight / 2));
119         }
120         g.drawLine((mpos * avcharWidth) + (avcharWidth / 2),
121                 (ypos + 2) - (avcharHeight / 2),
122                 (mpos * avcharWidth) + (avcharWidth / 2), ypos - 2);
123       }
124     }
125   }
126
127   private void drawWestScale(Graphics g, int startx, int endx, int ypos)
128   {
129     FontMetrics fm = getFontMetrics(av.getFont());
130     ypos += avcharHeight;
131     if (av.hasHiddenColumns())
132     {
133       startx = av.getAlignment().getHiddenColumns()
134               .visibleToAbsoluteColumn(startx);
135       endx = av.getAlignment().getHiddenColumns()
136               .visibleToAbsoluteColumn(endx);
137     }
138
139     // WEST SCALE
140     for (int i = 0; i < av.getAlignment().getHeight(); i++)
141     {
142       SequenceI seq = av.getAlignment().getSequenceAt(i);
143       int index = startx;
144       int value = -1;
145
146       while (index < endx)
147       {
148         if (jalview.util.Comparison.isGap(seq.getCharAt(index)))
149         {
150           index++;
151
152           continue;
153         }
154
155         value = av.getAlignment().getSequenceAt(i).findPosition(index);
156
157         break;
158       }
159
160       if (value != -1)
161       {
162         int x = LABEL_WEST - fm.stringWidth(String.valueOf(value))
163                 - avcharWidth / 2;
164         g.drawString(value + "", x,
165                 (ypos + (i * avcharHeight)) - (avcharHeight / 5));
166       }
167     }
168   }
169
170   private void drawEastScale(Graphics g, int startx, int endx, int ypos)
171   {
172     ypos += avcharHeight;
173
174     if (av.hasHiddenColumns())
175     {
176       endx = av.getAlignment().getHiddenColumns()
177               .visibleToAbsoluteColumn(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,
205                 (ypos + (i * avcharHeight)) - (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,
235             imgWidth - horizontal * avcharWidth,
236             imgHeight - vertical * avcharHeight, -horizontal * avcharWidth,
237             -vertical * avcharHeight);
238
239     int sr = ranges.getStartRes(), er = ranges.getEndRes(),
240             ss = ranges.getStartSeq(), es = ranges.getEndSeq(), transX = 0,
241             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)
305                     || (getSize().height != g.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     LABEL_EAST = 0;
418     LABEL_WEST = 0;
419   
420     if (av.getScaleRightWrapped())
421     {
422       LABEL_EAST = fm.stringWidth(getMask());
423     }
424   
425     if (av.getScaleLeftWrapped())
426     {
427       LABEL_WEST = fm.stringWidth(getMask());
428     }
429   
430     int hgap = avcharHeight;
431     if (av.getScaleAboveWrapped())
432     {
433       hgap += avcharHeight;
434     }
435   
436     int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / avcharWidth;
437     int cHeight = av.getAlignment().getHeight() * avcharHeight;
438   
439     av.setWrappedWidth(cWidth);
440   
441     av.getRanges().setViewportStartAndWidth(startRes, cWidth);
442   
443     int endx;
444     int ypos = hgap;
445   
446     int maxwidth = av.getAlignment().getVisibleWidth();
447   
448     while ((ypos <= canvasHeight) && (startRes < maxwidth))
449     {
450       endx = startRes + cWidth - 1;
451   
452       if (endx > maxwidth)
453       {
454         endx = maxwidth;
455       }
456   
457       g.setColor(Color.black);
458   
459       if (av.getScaleLeftWrapped())
460       {
461         drawWestScale(g, startRes, endx, ypos);
462       }
463   
464       if (av.getScaleRightWrapped())
465       {
466         g.translate(canvasWidth - LABEL_EAST, 0);
467         drawEastScale(g, startRes, endx, ypos);
468         g.translate(-(canvasWidth - LABEL_EAST), 0);
469       }
470   
471       g.translate(LABEL_WEST, 0);
472   
473       if (av.getScaleAboveWrapped())
474       {
475         drawNorthScale(g, startRes, endx, ypos);
476       }
477       if (av.hasHiddenColumns() && av.getShowHiddenMarkers())
478       {
479         HiddenColumns hidden = av.getAlignment().getHiddenColumns();
480         g.setColor(Color.blue);
481         int res;
482         Iterator<Integer> it = hidden.getStartRegionIterator(startRes,
483                 endx + 1);
484         while (it.hasNext())
485         {
486           res = it.next() - startRes;
487           gg.fillPolygon(
488                   new int[]
489                   { res * avcharWidth - avcharHeight / 4, res * avcharWidth + avcharHeight / 4, res * avcharWidth },
490                   new int[]
491                   { ypos - (avcharHeight / 2), ypos - (avcharHeight / 2), ypos - (avcharHeight / 2) + 8 }, 3);
492         }
493       }
494   
495       if (g.getClip() == null)
496       {
497         g.setClip(0, 0, cWidth * avcharWidth, canvasHeight);
498       }
499   
500       drawPanel(g, startRes, endx, 0, al.getHeight() - 1, ypos);
501       g.setClip(null);
502   
503       if (av.isShowAnnotation())
504       {
505         g.translate(0, cHeight + ypos + 4);
506         if (annotations == null)
507         {
508           annotations = new AnnotationPanel(av);
509         }
510   
511         annotations.drawComponent(g, startRes, endx + 1);
512         g.translate(0, -cHeight - ypos - 4);
513       }
514       g.translate(-LABEL_WEST, 0);
515   
516       ypos += cHeight + getAnnotationHeight() + hgap;
517   
518       startRes += cWidth;
519     }
520   
521   }
522
523   AnnotationPanel annotations;
524
525   int getAnnotationHeight()
526   {
527     if (!av.isShowAnnotation())
528     {
529       return 0;
530     }
531
532     if (annotations == null)
533     {
534       annotations = new AnnotationPanel(av);
535     }
536
537     return annotations.adjustPanelHeight();
538   }
539
540   private void drawPanel(Graphics g1, final int startRes, final int endRes,
541           final int startSeq, final int endSeq, final int offset)
542   {
543
544     if (!av.hasHiddenColumns())
545     {
546       draw(g1, startRes, endRes, startSeq, endSeq, offset);
547     }
548     else
549     {
550       int screenY = 0;
551       int blockStart;
552       int blockEnd;
553
554       HiddenColumns hidden = av.getAlignment().getHiddenColumns();
555       VisibleContigsIterator regions = hidden
556               .getVisContigsIterator(startRes, endRes + 1, true);
557
558       while (regions.hasNext())
559       {
560         int[] region = regions.next();
561         blockEnd = region[1];
562         blockStart = region[0];
563
564         /*
565          * draw up to just before the next hidden region, or the end of
566          * the visible region, whichever comes first
567          */
568         g1.translate(screenY * avcharWidth, 0);
569
570         draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
571
572         /*
573          * draw the downline of the hidden column marker (ScalePanel draws the
574          * triangle on top) if we reached it
575          */
576         if (av.getShowHiddenMarkers()
577                 && (regions.hasNext() || regions.endsAtHidden()))
578         {
579           g1.setColor(Color.blue);
580           g1.drawLine((blockEnd - blockStart + 1) * avcharWidth - 1,
581                   0 + offset, (blockEnd - blockStart + 1) * avcharWidth - 1,
582                   (endSeq - startSeq + 1) * avcharHeight + offset);
583         }
584
585         g1.translate(-screenY * avcharWidth, 0);
586         screenY += blockEnd - blockStart + 1;
587       }
588     }
589   }
590
591   // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
592   // int x1, int x2, int y1, int y2, int startx, int starty,
593   void draw(Graphics g, int startRes, int endRes, int startSeq, int endSeq,
594           int offset)
595   {
596     g.setFont(av.getFont());
597     sr.prepare(g, av.isRenderGaps());
598     updateViewport();
599     SequenceI nextSeq;
600
601     // / First draw the sequences
602     // ///////////////////////////
603     for (int i = startSeq; i <= endSeq; i++)
604     {
605       nextSeq = av.getAlignment().getSequenceAt(i);
606
607       if (nextSeq == null)
608       {
609         continue;
610       }
611
612       sr.drawSequence(nextSeq, av.getAlignment().findAllGroups(nextSeq),
613               startRes, endRes, offset + ((i - startSeq) * avcharHeight));
614
615       if (av.isShowSequenceFeatures())
616       {
617         fr.drawSequence(g, nextSeq, startRes, endRes,
618                 offset + ((i - startSeq) * avcharHeight), false);
619       }
620
621       // / Highlight search Results once all sequences have been drawn
622       // ////////////////////////////////////////////////////////
623       if (av.hasSearchResults())
624       {
625         int[] visibleResults = av.getSearchResults().getResults(nextSeq,
626                 startRes, endRes);
627         if (visibleResults != null)
628         {
629           for (int r = 0; r < visibleResults.length; r += 2)
630           {
631             sr.drawHighlightedText(nextSeq, visibleResults[r],
632                     visibleResults[r + 1],
633                     (visibleResults[r] - startRes) * avcharWidth,
634                     offset + ((i - startSeq) * avcharHeight));
635           }
636         }
637       }
638
639       if (av.cursorMode && cursorY == i && cursorX >= startRes
640               && cursorX <= endRes)
641       {
642         sr.drawCursor(nextSeq, cursorX, (cursorX - startRes) * avcharWidth,
643                 offset + ((i - startSeq) * avcharHeight));
644       }
645     }
646
647     if (av.getSelectionGroup() != null
648             || av.getAlignment().getGroups().size() > 0)
649     {
650       drawGroupsBoundaries(g, startRes, endRes, startSeq, endSeq, offset);
651     }
652
653   }
654
655   private void drawGroupsBoundaries(Graphics g, int startRes, int endRes,
656           int startSeq, int endSeq, int offset)
657   {
658     //
659     // ///////////////////////////////////
660     // Now outline any areas if necessary
661     // ///////////////////////////////////
662     SequenceGroup group = av.getSelectionGroup();
663
664     int sx = -1;
665     int sy = -1;
666     int ex = -1;
667     int groupIndex = -1;
668
669     if ((group == null) && (av.getAlignment().getGroups().size() > 0))
670     {
671       group = av.getAlignment().getGroups().get(0);
672       groupIndex = 0;
673     }
674
675     if (group != null)
676     {
677       do
678       {
679         int oldY = -1;
680         int i = 0;
681         boolean inGroup = false;
682         int top = -1;
683         int bottom = -1;
684         int alHeight = av.getAlignment().getHeight() - 1;
685
686         for (i = startSeq; i <= endSeq; i++)
687         {
688           sx = (group.getStartRes() - startRes) * avcharWidth;
689           sy = offset + ((i - startSeq) * avcharHeight);
690           ex = (((group.getEndRes() + 1) - group.getStartRes())
691                   * avcharWidth) - 1;
692
693           if (sx + ex < 0 || sx > imgWidth)
694           {
695             continue;
696           }
697
698           if ((sx <= (endRes - startRes) * avcharWidth)
699                   && group.getSequences(null)
700                           .contains(av.getAlignment().getSequenceAt(i)))
701           {
702             if ((bottom == -1)
703                     && (i >= alHeight || !group.getSequences(null).contains(
704                             av.getAlignment().getSequenceAt(i + 1))))
705             {
706               bottom = sy + avcharHeight;
707             }
708
709             if (!inGroup)
710             {
711               if (((top == -1) && (i == 0)) || !group.getSequences(null)
712                       .contains(av.getAlignment().getSequenceAt(i - 1)))
713               {
714                 top = sy;
715               }
716
717               oldY = sy;
718               inGroup = true;
719
720               if (group == av.getSelectionGroup())
721               {
722                 g.setColor(Color.red);
723               }
724               else
725               {
726                 g.setColor(group.getOutlineColour());
727               }
728             }
729           }
730           else
731           {
732             if (inGroup)
733             {
734               if (sx >= 0 && sx < imgWidth)
735               {
736                 g.drawLine(sx, oldY, sx, sy);
737               }
738
739               if (sx + ex < imgWidth)
740               {
741                 g.drawLine(sx + ex, oldY, sx + ex, sy);
742               }
743
744               if (sx < 0)
745               {
746                 ex += sx;
747                 sx = 0;
748               }
749
750               if (sx + ex > imgWidth)
751               {
752                 ex = imgWidth;
753               }
754
755               else if (sx + ex >= (endRes - startRes + 1) * avcharWidth)
756               {
757                 ex = (endRes - startRes + 1) * avcharWidth;
758               }
759
760               if (top != -1)
761               {
762                 g.drawLine(sx, top, sx + ex, top);
763                 top = -1;
764               }
765
766               if (bottom != -1)
767               {
768                 g.drawLine(sx, bottom, sx + ex, bottom);
769                 bottom = -1;
770               }
771
772               inGroup = false;
773             }
774           }
775         }
776
777         if (inGroup)
778         {
779           sy = offset + ((i - startSeq) * avcharHeight);
780           if (sx >= 0 && sx < imgWidth)
781           {
782             g.drawLine(sx, oldY, sx, sy);
783           }
784
785           if (sx + ex < imgWidth)
786           {
787             g.drawLine(sx + ex, oldY, sx + ex, sy);
788           }
789
790           if (sx < 0)
791           {
792             ex += sx;
793             sx = 0;
794           }
795
796           if (sx + ex > imgWidth)
797           {
798             ex = imgWidth;
799           }
800           else if (sx + ex >= (endRes - startRes + 1) * avcharWidth)
801           {
802             ex = (endRes - startRes + 1) * avcharWidth;
803           }
804
805           if (top != -1)
806           {
807             g.drawLine(sx, top, sx + ex, top);
808             top = -1;
809           }
810
811           if (bottom != -1)
812           {
813             g.drawLine(sx, bottom - 1, sx + ex, bottom - 1);
814             bottom = -1;
815           }
816
817           inGroup = false;
818         }
819
820         groupIndex++;
821
822         if (groupIndex >= av.getAlignment().getGroups().size())
823         {
824           break;
825         }
826
827         group = av.getAlignment().getGroups().get(groupIndex);
828       } while (groupIndex < av.getAlignment().getGroups().size());
829
830     }
831   }
832
833   public void highlightSearchResults(SearchResultsI results)
834   {
835     av.setSearchResults(results);
836     repaint();
837   }
838
839   @Override
840   public void propertyChange(PropertyChangeEvent evt)
841   {
842     String eventName = evt.getPropertyName();
843
844     if (eventName.equals(SequenceGroup.SEQ_GROUP_CHANGED))
845     {
846       fastPaint = true;
847       repaint();
848       return;
849     }
850     else if (eventName.equals(ViewportRanges.MOVE_VIEWPORT))
851     {
852       fastPaint = false;
853       repaint();
854       return;
855     }
856
857     if (!av.getWrapAlignment())
858     {
859       int scrollX = 0;
860       if (eventName.equals(ViewportRanges.STARTRES)
861               || eventName.equals(ViewportRanges.STARTRESANDSEQ))
862       {
863         // Make sure we're not trying to draw a panel
864         // larger than the visible window
865         if (eventName.equals(ViewportRanges.STARTRES))
866         {
867           scrollX = (int) evt.getNewValue() - (int) evt.getOldValue();
868         }
869         else
870         {
871           scrollX = ((int[]) evt.getNewValue())[0]
872                   - ((int[]) evt.getOldValue())[0];
873         }
874         ViewportRanges vpRanges = av.getRanges();
875         int range = vpRanges.getEndRes() - vpRanges.getStartRes();
876         if (scrollX > range)
877         {
878           scrollX = range;
879         }
880         else if (scrollX < -range)
881         {
882           scrollX = -range;
883         }
884       }
885
886       // Both scrolling and resizing change viewport ranges: scrolling changes
887       // both start and end points, but resize only changes end values.
888       // Here we only want to fastpaint on a scroll, with resize using a normal
889       // paint, so scroll events are identified as changes to the horizontal or
890       // vertical start value.
891       if (eventName.equals(ViewportRanges.STARTRES))
892       {
893         // scroll - startres and endres both change
894         fastPaint(scrollX, 0);
895       }
896       else if (eventName.equals(ViewportRanges.STARTSEQ))
897       {
898         // scroll
899         fastPaint(0, (int) evt.getNewValue() - (int) evt.getOldValue());
900       }
901       else if (eventName.equals(ViewportRanges.STARTRESANDSEQ))
902       {
903         fastPaint(scrollX, 0);
904       }
905     }
906   }
907
908 }