summaryrefslogtreecommitdiff
path: root/javax/swing/JViewport.java
blob: 9fd14e80a6291b60ab92758c2875508ceffdcb2b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
/* JViewport.java -- 
   Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */


package javax.swing;

import gnu.classpath.SystemProperties;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.io.Serializable;

import javax.accessibility.Accessible;
import javax.accessibility.AccessibleContext;
import javax.accessibility.AccessibleRole;
import javax.swing.border.Border;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.ViewportUI;

/**
 *  
 * <pre>
 *                                                     _
 *   +-------------------------------+    ...........Y1 \
 *   |  view                         |                .  \
 *   |  (this component's child)     |                .   > VY
 *   |                               |                .  / = Y2-Y1
 *   |         +------------------------------+  ....Y2_/
 *   |         | viewport            |        |       .
 *   |         | (this component)    |        |       .
 *   |         |                     |        |       .
 *   |         |                     |        |       .
 *   |         |                     |        |       .
 *   |         |                     |        |       .
 *   |         +------------------------------+  ....Y3
 *   |                               |                .
 *   |         .                     |        .       .
 *   |         .                     |        .       .
 *   +---------.---------------------+    ...........Y4
 *   .         .                     .        .
 *   .         .                     .        .
 *   .         .                     .        .
 *   X1.......X2.....................X3.......X4
 *   \____  ___/
 *        \/
 *        VX = X2-X1
 *</pre>
 *  
 * <p>A viewport is, like all swing components, located at some position in
 * the swing component tree; that location is exactly the same as any other
 * components: the viewport's "bounds".</p>
 *
 * <p>But in terms of drawing its child, the viewport thinks of itself as
 * covering a particular position <em>of the view's coordinate space</em>.
 * For example, the {@link #getViewPosition} method returns
 * the position <code>(VX,VY)</code> shown above, which is an position in
 * "view space", even though this is <em>implemented</em> by positioning
 * the underlying child at position <code>(-VX,-VY)</code></p>
 *
 */
public class JViewport extends JComponent implements Accessible
{
  /**
   * Provides accessibility support for <code>JViewport</code>.
   *
   * @author Roman Kennke (roman@kennke.org)
   */
  protected class AccessibleJViewport extends AccessibleJComponent
  {
    /**
     * Creates a new instance of <code>AccessibleJViewport</code>.
     */
    protected AccessibleJViewport()
    {
      // Nothing to do here.
    }

    /**
     * Returns the accessible role of <code>JViewport</code>, which is
     * {@link AccessibleRole#VIEWPORT}.
     *
     * @return the accessible role of <code>JViewport</code>
     */
    public AccessibleRole getAccessibleRole()
    {
      return AccessibleRole.VIEWPORT;
    }
  }

  /**
   * A {@link java.awt.event.ComponentListener} that listens for
   * changes of the view's size. This triggers a revalidate() call on the
   * viewport.
   */
  protected class ViewListener extends ComponentAdapter implements Serializable
  {
    private static final long serialVersionUID = -2812489404285958070L;

    /**
     * Creates a new instance of ViewListener.
     */
    protected ViewListener()
    {
      // Nothing to do here.
    }

    /**
     * Receives notification when a component (in this case: the view
     * component) changes it's size. This simply triggers a revalidate() on the
     * viewport.
     *
     * @param ev the ComponentEvent describing the change
     */
    public void componentResized(ComponentEvent ev)
    {
      // Fire state change, because resizing the view means changing the
      // extentSize.
      fireStateChanged();
      revalidate();
    }
  }

  public static final int SIMPLE_SCROLL_MODE = 0;
  public static final int BLIT_SCROLL_MODE = 1;
  public static final int BACKINGSTORE_SCROLL_MODE = 2;

  private static final long serialVersionUID = -6925142919680527970L;

  /**
   * The default scrollmode to be used by all JViewports as determined by
   * the system property gnu.javax.swing.JViewport.scrollMode.
   */
  private static final int defaultScrollMode;

  protected boolean scrollUnderway;
  protected boolean isViewSizeSet;

  /**
   * This flag indicates whether we use a backing store for drawing.
   *
   * @deprecated since JDK 1.3
   */
  protected boolean backingStore;

  /**
   * The backingstore image used for the backingstore and blit scroll methods.
   */
  protected Image backingStoreImage;

  /**
   * The position at which the view has been drawn the last time. This is used
   * to determine the bittable area.
   */
  protected Point lastPaintPosition;

  ChangeEvent changeEvent = new ChangeEvent(this);

  int scrollMode;

  /**
   * The ViewListener instance.
   */
  ViewListener viewListener;

  /**
   * Stores the location from where to blit. This is a cached Point object used
   * in blitting calculations.
   */
  Point cachedBlitFrom;

  /**
   * Stores the location where to blit to. This is a cached Point object used
   * in blitting calculations.
   */
  Point cachedBlitTo;

  /**
   * Stores the width of the blitted area. This is a cached Dimension object
   * used in blitting calculations.
   */
  Dimension cachedBlitSize;

  /**
   * Stores the bounds of the area that needs to be repainted. This is a cached
   * Rectangle object used in blitting calculations. 
   */
  Rectangle cachedBlitPaint;

  boolean damaged = true;

  /**
   * A flag indicating if the size of the viewport has changed since the
   * last repaint. This is used in double buffered painting to check if we
   * need a new double buffer, or can reuse the old one.
   */
  boolean sizeChanged = true;

  /**
   * Indicates if this JViewport is the paint root or not. If it is not, then
   * we may not assume that the offscreen buffer still has the right content
   * because parent components may have cleared the background already.
   */
  private boolean isPaintRoot = false;

  /**
   * Initializes the default setting for the scrollMode property.
   */
  static
  {
    String scrollModeProp =
      SystemProperties.getProperty("gnu.swing.scrollmode", "BACKINGSTORE");
    if (scrollModeProp.equalsIgnoreCase("simple"))
      defaultScrollMode = SIMPLE_SCROLL_MODE;
    else if (scrollModeProp.equalsIgnoreCase("backingstore"))
      defaultScrollMode = BACKINGSTORE_SCROLL_MODE;
    else
      defaultScrollMode = BLIT_SCROLL_MODE;
  }

  public JViewport()
  {
    setOpaque(true);
    setScrollMode(defaultScrollMode);
    updateUI();
    setLayout(createLayoutManager());
    lastPaintPosition = new Point();
    cachedBlitFrom = new Point();
    cachedBlitTo = new Point();
    cachedBlitSize = new Dimension();
    cachedBlitPaint = new Rectangle();
  }

  public Dimension getExtentSize()
  {
    return getSize();
  }

  public Dimension toViewCoordinates(Dimension size)
  {
    return size;
  }

  public Point toViewCoordinates(Point p)
  {
    Point pos = getViewPosition();
    return new Point(p.x + pos.x,
                     p.y + pos.y);
  }

  public void setExtentSize(Dimension newSize)
  {
    Dimension oldExtent = getExtentSize();
    if (! newSize.equals(oldExtent))
      {
        setSize(newSize);
        fireStateChanged();
      }
  }

  /**
   * Returns the viewSize when set, or the preferred size of the set
   * Component view.  If no viewSize and no Component view is set an
   * empty Dimension is returned.
   */
  public Dimension getViewSize()
  {
    Dimension size; 
    Component view = getView();
    if (view != null)
      {
        if (isViewSizeSet)
          size = view.getSize();
        else
	  size = view.getPreferredSize();
      }
    else
      size = new Dimension(0, 0);
    return size;
  }


  public void setViewSize(Dimension newSize)
  {
    Component view = getView();
    if (view != null)
      {
        if (! newSize.equals(view.getSize()))
          {
            scrollUnderway = false;
            view.setSize(newSize);
            isViewSizeSet = true;
            fireStateChanged();
          }
      }
  }

  /**
   * Get the viewport's position in view space. Despite confusing name,
   * this really does return the viewport's (0,0) position in view space,
   * not the view's position.
   */

  public Point getViewPosition()
  {
    Component view = getView();
    if (view == null)
      return new Point(0,0);
    else
      {
        Point p = view.getLocation();
        p.x = -p.x;
        p.y = -p.y;
        return p;
      }
  }

  public void setViewPosition(Point p)
  {
    Component view = getView();
    if (view != null && ! p.equals(getViewPosition()))
      {
        scrollUnderway = true;
        view.setLocation(-p.x, -p.y);
        fireStateChanged();
      }
  }

  public Rectangle getViewRect()
  {
    return new Rectangle(getViewPosition(), getExtentSize());
  }

  /**
   * @deprecated 1.4
   */
  public boolean isBackingStoreEnabled()
  {
    return scrollMode == BACKINGSTORE_SCROLL_MODE;
  }

  /**
   * @deprecated 1.4
   */
  public void setBackingStoreEnabled(boolean b)
  {
    if (b && scrollMode != BACKINGSTORE_SCROLL_MODE)
      {
        scrollMode = BACKINGSTORE_SCROLL_MODE;
        fireStateChanged();
      }
  }

  public void setScrollMode(int mode)
  {
    scrollMode = mode;
    fireStateChanged();
  }

  public int getScrollMode()
  {
    return scrollMode;
  }

  public Component getView()
  {
    if (getComponentCount() == 0)
      return null;
  
    return getComponents()[0];
  }

  public void setView(Component v)
  {
    Component currView = getView();
    if (viewListener != null && currView != null)
      currView.removeComponentListener(viewListener);

    if (v != null)
      {
        if (viewListener == null)
          viewListener = createViewListener();
        v.addComponentListener(viewListener);
        add(v);
        fireStateChanged();
      }
    revalidate();
    repaint();
  }

  public void reshape(int x, int y, int w, int h)
  {
    if (w != getWidth() || h != getHeight())
      sizeChanged = true;
    super.reshape(x, y, w, h);
    if (sizeChanged)
      {
        damaged = true;
        fireStateChanged();
      }
  }

  public final Insets getInsets()
  {
    return new Insets(0, 0, 0, 0);
  }

  public final Insets getInsets(Insets insets)
  {
    if (insets == null)
      return getInsets();
    insets.top = 0;
    insets.bottom = 0;
    insets.left = 0;
    insets.right = 0;
    return insets;
  }
    

  /**
   * Overridden to return <code>false</code>, so the JViewport's paint method
   * gets called instead of directly calling the children. This is necessary
   * in order to get a useful clipping and translation on the children.
   *
   * @return <code>false</code>
   */
  public boolean isOptimizedDrawingEnabled()
  {
    return false;
  }

  public void paint(Graphics g)
  {
    Component view = getView();

    if (view == null)
      return;

    Rectangle viewBounds = view.getBounds();
    Rectangle portBounds = getBounds();

    if (viewBounds.width == 0 
        || viewBounds.height == 0
        || portBounds.width == 0
        || portBounds.height == 0)
      return;

    switch (getScrollMode())
      {

      case JViewport.BACKINGSTORE_SCROLL_MODE:
        paintBackingStore(g);
        break;
      case JViewport.BLIT_SCROLL_MODE:
        paintBlit(g);
        break;
      case JViewport.SIMPLE_SCROLL_MODE:
      default:
        paintSimple(g);
        break;
      }
    damaged = false;
  }

  public void addChangeListener(ChangeListener listener)
  {
    listenerList.add(ChangeListener.class, listener);
  }

  public void removeChangeListener(ChangeListener listener)
  {
    listenerList.remove(ChangeListener.class, listener);
  }

  public ChangeListener[] getChangeListeners() 
  {
    return (ChangeListener[]) getListeners(ChangeListener.class);
  }

  /**
   * This method returns the String ID of the UI class of  Separator.
   *
   * @return The UI class' String ID.
   */
  public String getUIClassID()
  {
    return "ViewportUI";
  }

  /**
   * This method resets the UI used to the Look and Feel defaults..
   */
  public void updateUI()
  {
    setUI((ViewportUI) UIManager.getUI(this));
  }            

  /**
   * This method returns the viewport's UI delegate.
   *
   * @return The viewport's UI delegate.
   */
  public ViewportUI getUI()
  {
    return (ViewportUI) ui;
  }

  /**
   * This method sets the viewport's UI delegate.
   *
   * @param ui The viewport's UI delegate.
   */
  public void setUI(ViewportUI ui)
  {
    super.setUI(ui);
  }

  public final void setBorder(Border border)
  {
    if (border != null)
      throw new IllegalArgumentException();
  }

  /**
   * Scrolls the view so that contentRect becomes visible.
   *
   * @param contentRect the rectangle to make visible within the view
   */
  public void scrollRectToVisible(Rectangle contentRect)
  {
    Component view = getView();
    if (view == null)
      return;    
      
    Point pos = getViewPosition();
    Rectangle viewBounds = getView().getBounds();
    Rectangle portBounds = getBounds();
    
    if (isShowing())
      getView().validate();

    // If the bottom boundary of contentRect is below the port
    // boundaries, scroll up as necessary.
    if (contentRect.y + contentRect.height + viewBounds.y > portBounds.height)
      pos.y = contentRect.y + contentRect.height - portBounds.height;
    // If contentRect.y is above the port boundaries, scroll down to
    // contentRect.y.
    if (contentRect.y + viewBounds.y < 0)
      pos.y = contentRect.y;
    // If the right boundary of contentRect is right from the port
    // boundaries, scroll left as necessary.
    if (contentRect.x + contentRect.width + viewBounds.x > portBounds.width)
      pos.x = contentRect.x + contentRect.width - portBounds.width;
    // If contentRect.x is left from the port boundaries, scroll right to
    // contentRect.x.
    if (contentRect.x + viewBounds.x < 0)
      pos.x = contentRect.x;
    setViewPosition(pos);
  }

  /**
   * Returns the accessible context for this <code>JViewport</code>. This
   * will be an instance of {@link AccessibleJViewport}.
   *
   * @return the accessible context for this <code>JViewport</code>
   */
  public AccessibleContext getAccessibleContext()
  {
    if (accessibleContext == null)
      accessibleContext = new AccessibleJViewport();
    return accessibleContext;
  }

  /**
   * Forward repaint to parent to make sure only one paint is performed by the
   * RepaintManager.
   *
   * @param tm number of milliseconds to defer the repaint request
   * @param x the X coordinate of the upper left corner of the dirty area
   * @param y the Y coordinate of the upper left corner of the dirty area
   * @param w the width of the dirty area
   * @param h the height of the dirty area
   */
  public void repaint(long tm, int x, int y, int w, int h)
  {
    Component parent = getParent();
    if (parent != null)
      parent.repaint(tm, x + getX(), y + getY(), w, h);
    else
      super.repaint(tm, x, y, w, h);
  }

  protected void addImpl(Component comp, Object constraints, int index)
  {
    if (getComponentCount() > 0)
      remove(getComponents()[0]);
    
    super.addImpl(comp, constraints, index);
  }

  protected void fireStateChanged()
  {
    ChangeListener[] listeners = getChangeListeners();
    for (int i = 0; i < listeners.length; ++i)
      listeners[i].stateChanged(changeEvent);
  }

  /**
   * Creates a {@link ViewListener} that is supposed to listen for
   * size changes on the view component.
   *
   * @return a ViewListener instance
   */
  protected ViewListener createViewListener()
  {
    return new ViewListener();
  }

  /**
   * Creates the LayoutManager that is used for this viewport. Override
   * this method if you want to use a custom LayoutManager.
   *
   * @return a LayoutManager to use for this viewport
   */
  protected LayoutManager createLayoutManager()
  {
    return new ViewportLayout();
  }

  /**
   * Computes the parameters for the blitting scroll method. <code>dx</code>
   * and <code>dy</code> specifiy the X and Y offset by which the viewport
   * is scrolled. All other arguments are output parameters and are filled by
   * this method.
   *
   * <code>blitFrom</code> holds the position of the blit rectangle in the
   * viewport rectangle before scrolling, <code>blitTo</code> where the blitArea
   * is copied to.
   *
   * <code>blitSize</code> holds the size of the blit area and
   * <code>blitPaint</code> is the area of the view that needs to be painted.
   *
   * This method returns <code>true</code> if blitting is possible and
   * <code>false</code> if the viewport has to be repainted completetly without
   * blitting.
   *
   * @param dx the horizontal delta
   * @param dy the vertical delta
   * @param blitFrom the position from where to blit; set by this method
   * @param blitTo the position where to blit area is copied to; set by this
   *        method
   * @param blitSize the size of the blitted area; set by this method
   * @param blitPaint the area that needs repainting; set by this method
   *
   * @return <code>true</code> if blitting is possible,
   *         <code>false</code> otherwise
   */
  protected boolean computeBlit(int dx, int dy, Point blitFrom, Point blitTo,
                                Dimension blitSize, Rectangle blitPaint)
  {
    if ((dx != 0 && dy != 0) || (dy == 0 && dy == 0) || damaged)
      // We cannot blit if the viewport is scrolled in both directions at
      // once. Also, we do not want to blit if the viewport is not scrolled at
      // all, because that probably means the view component repaints itself
      // and the buffer needs updating.
      return false;

    Rectangle portBounds = SwingUtilities.calculateInnerArea(this, getBounds());

    // Compute the blitFrom and blitTo parameters.
    blitFrom.x = portBounds.x;
    blitFrom.y = portBounds.y;
    blitTo.x = portBounds.x;
    blitTo.y = portBounds.y;

    if (dy > 0)
      {
        blitFrom.y = portBounds.y + dy;
      }
    else if (dy < 0)
      {
        blitTo.y = portBounds.y - dy;
      }
    else if (dx > 0)
      {
        blitFrom.x = portBounds.x + dx;
      }
    else if (dx < 0)
      {
        blitTo.x = portBounds.x - dx;
      }

    // Compute size of the blit area.
    if (dx != 0)
      {
        blitSize.width = portBounds.width - Math.abs(dx);
        blitSize.height = portBounds.height;
      }
    else if (dy != 0)
      {
        blitSize.width = portBounds.width;
        blitSize.height = portBounds.height - Math.abs(dy);
      }

    // Compute the blitPaint parameter.
    blitPaint.setBounds(portBounds);
    if (dy > 0)
      {
        blitPaint.y = portBounds.y + portBounds.height - dy;
        blitPaint.height = dy;
      }
    else if (dy < 0)
      {
        blitPaint.height = -dy;
      }
    if (dx > 0)
      {
        blitPaint.x = portBounds.x + portBounds.width - dx;
        blitPaint.width = dx;
      }
    else if (dx < 0)
      {
        blitPaint.width = -dx;
      }

    return true;
  }

  /**
   * Paints the viewport in case we have a scrollmode of
   * {@link #SIMPLE_SCROLL_MODE}.
   *
   * This simply paints the view directly on the surface of the viewport.
   *
   * @param g the graphics context to use
   */
  void paintSimple(Graphics g)
  {
    // We need to call this to properly clear the background.
    paintComponent(g);

    Point pos = getViewPosition();
    Component view = getView();
    Shape oldClip = g.getClip();
    g.clipRect(0, 0, getWidth(), getHeight());
    boolean translated = false;
    try
      {
        g.translate(-pos.x, -pos.y);
        translated = true;
        view.paint(g);
      } 
    finally
      {
        if (translated)
          g.translate (pos.x, pos.y);
        g.setClip(oldClip);
      }
  }

  /**
   * Paints the viewport in case we have a scroll mode of
   * {@link #BACKINGSTORE_SCROLL_MODE}.
   *
   * This method uses a backing store image to paint the view to, which is then
   * subsequently painted on the screen. This should make scrolling more
   * smooth.
   *
   * @param g the graphics context to use
   */
  void paintBackingStore(Graphics g)
  {
    // If we have no backing store image yet or the size of the component has
    // changed, we need to rebuild the backing store.
    if (backingStoreImage == null || sizeChanged)
      {
        backingStoreImage = createImage(getWidth(), getHeight());
        sizeChanged = false;
        Graphics g2 = backingStoreImage.getGraphics();
        paintSimple(g2);
        g2.dispose();
      }
    // Otherwise we can perform the blitting on the backing store image:
    // First we move the part that remains visible after scrolling, then
    // we only need to paint the bit that becomes newly visible.
    else
      {
        Graphics g2 = backingStoreImage.getGraphics();
        Point viewPosition = getViewPosition();
        int dx = viewPosition.x - lastPaintPosition.x;
        int dy = viewPosition.y - lastPaintPosition.y;
        boolean canBlit = computeBlit(dx, dy, cachedBlitFrom, cachedBlitTo,
                                      cachedBlitSize, cachedBlitPaint);
        if (canBlit)
          {
            // Copy the part that remains visible during scrolling.
            g2.copyArea(cachedBlitFrom.x, cachedBlitFrom.y,
                        cachedBlitSize.width, cachedBlitSize.height,
                        cachedBlitTo.x - cachedBlitFrom.x,
                        cachedBlitTo.y - cachedBlitFrom.y);
            // Now paint the part that becomes newly visible.
            g2.setClip(cachedBlitPaint.x, cachedBlitPaint.y,
                       cachedBlitPaint.width, cachedBlitPaint.height);
            paintSimple(g2);
          }
        // If blitting is not possible for some reason, fall back to repainting
        // everything.
        else
          {
            // If the image has not been scrolled at all, only the changed
            // clip must be updated in the buffer.
            if (dx == 0 && dy == 0)
              g2.setClip(g.getClip());
            
            paintSimple(g2);
          }
        g2.dispose();
      }
    // Actually draw the backingstore image to the graphics context.
    g.drawImage(backingStoreImage, 0, 0, this);
    // Update the lastPaintPosition so that we know what is already drawn when
    // we paint the next time.
    lastPaintPosition.setLocation(getViewPosition());
  }

  /**
   * Paints the viewport in case we have a scrollmode of
   * {@link #BLIT_SCROLL_MODE}.
   *
   * This paints the viewport using a backingstore and a blitting algorithm.
   * Only the newly exposed area of the view is painted from the view painting
   * methods, the remainder is copied from the backing store.
   *
   * @param g the graphics context to use
   */
  void paintBlit(Graphics g)
  {
    // First we move the part that remains visible after scrolling, then
    // we only need to paint the bit that becomes newly visible.
    Point viewPosition = getViewPosition();
    int dx = viewPosition.x - lastPaintPosition.x;
    int dy = viewPosition.y - lastPaintPosition.y;
    boolean canBlit = computeBlit(dx, dy, cachedBlitFrom, cachedBlitTo,
                                  cachedBlitSize, cachedBlitPaint);
    if (canBlit && isPaintRoot)
      {
        // Copy the part that remains visible during scrolling.
        g.copyArea(cachedBlitFrom.x, cachedBlitFrom.y,
                   cachedBlitSize.width, cachedBlitSize.height,
                   cachedBlitTo.x - cachedBlitFrom.x,
                   cachedBlitTo.y - cachedBlitFrom.y);
        // Now paint the part that becomes newly visible.
        Shape oldClip = g.getClip();
        g.clipRect(cachedBlitPaint.x, cachedBlitPaint.y,
                  cachedBlitPaint.width, cachedBlitPaint.height);
        try
          {
            paintSimple(g);
          }
        finally
          {
            g.setClip(oldClip);
          }
      }
    // If blitting is not possible for some reason, fall back to repainting
    // everything.
    else
      paintSimple(g);
    lastPaintPosition.setLocation(getViewPosition());
  }

  /**
   * Overridden from JComponent to set the {@link #isPaintRoot} flag.
   *
   * @param x the rectangle to paint, X coordinate
   * @param y the rectangle to paint, Y coordinate
   * @param w the rectangle to paint, width
   * @param h the rectangle to paint, height
   */
  void paintImmediately2(int x, int y, int w, int h)
  {
    isPaintRoot = true;
    super.paintImmediately2(x, y, w, h);
    isPaintRoot = false;
  }
}