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