diff -r fd16c54261b3 -r 90ce3da70b43 jdk/src/share/classes/javax/swing/JViewport.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/share/classes/javax/swing/JViewport.java Sat Dec 01 00:00:00 2007 +0000 @@ -0,0 +1,1789 @@ +/* + * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package javax.swing; + +import java.awt.*; +import java.awt.event.*; +import java.awt.image.VolatileImage; +import java.awt.peer.ComponentPeer; +import java.applet.Applet; +import javax.swing.plaf.ViewportUI; + +import javax.swing.event.*; +import javax.swing.border.*; +import javax.accessibility.*; + + +import java.io.Serializable; + + +/** + * The "viewport" or "porthole" through which you see the underlying + * information. When you scroll, what moves is the viewport. It is like + * peering through a camera's viewfinder. Moving the viewfinder upwards + * brings new things into view at the top of the picture and loses + * things that were at the bottom. + *
+ * By default, JViewport
is opaque. To change this, use the
+ * setOpaque
method.
+ *
+ * NOTE:We have implemented a faster scrolling algorithm that + * does not require a buffer to draw in. The algorithm works as follows: + *
JComponents
,
+ * if they aren't, stop and repaint the whole viewport.
+ * Window
's graphics and
+ * do a copyArea
on the scrolled region.
+ * copyArea
s.
+ * Compared to the non backing store case this
+ * approach will greatly reduce the painted region.
+ * + * This approach can cause slower times than the backing store approach + * when the viewport is obscured by another window, or partially offscreen. + * When another window + * obscures the viewport the copyArea will copy garbage and a + * paint event will be generated by the system to inform us we need to + * paint the newly exposed region. The only way to handle this is to + * repaint the whole viewport, which can cause slower performance than the + * backing store case. In most applications very rarely will the user be + * scrolling while the viewport is obscured by another window or offscreen, + * so this optimization is usually worth the performance hit when obscured. + *
+ * Warning: Swing is not thread safe. For more + * information see Swing's Threading + * Policy. + *
+ * Warning:
+ * Serialized objects of this class will not be compatible with
+ * future Swing releases. The current serialization support is
+ * appropriate for short term storage or RMI between applications running
+ * the same version of Swing. As of 1.4, support for long term storage
+ * of all JavaBeansTM
+ * has been added to the java.beans
package.
+ * Please see {@link java.beans.XMLEncoder}.
+ *
+ * @author Hans Muller
+ * @author Philip Milne
+ * @see JScrollPane
+ */
+public class JViewport extends JComponent implements Accessible
+{
+ /**
+ * @see #getUIClassID
+ * @see #readObject
+ */
+ private static final String uiClassID = "ViewportUI";
+
+ /** Property used to indicate window blitting should not be done.
+ */
+ static final Object EnableWindowBlit = "EnableWindowBlit";
+
+ /**
+ * True when the viewport dimensions have been determined.
+ * The default is false.
+ */
+ protected boolean isViewSizeSet = false;
+
+ /**
+ * The last viewPosition
that we've painted, so we know how
+ * much of the backing store image is valid.
+ */
+ protected Point lastPaintPosition = null;
+
+ /**
+ * True when this viewport is maintaining an offscreen image of its
+ * contents, so that some scrolling can take place using fast "bit-blit"
+ * operations instead of by accessing the view object to construct the
+ * display. The default is false
.
+ *
+ * @deprecated As of Java 2 platform v1.3
+ * @see #setScrollMode
+ */
+ @Deprecated
+ protected boolean backingStore = false;
+
+ /** The view image used for a backing store. */
+ transient protected Image backingStoreImage = null;
+
+ /**
+ * The scrollUnderway
flag is used for components like
+ * JList
. When the downarrow key is pressed on a
+ * JList
and the selected
+ * cell is the last in the list, the scrollpane
autoscrolls.
+ * Here, the old selected cell needs repainting and so we need
+ * a flag to make the viewport do the optimized painting
+ * only when there is an explicit call to
+ * setViewPosition(Point)
.
+ * When setBounds
is called through other routes,
+ * the flag is off and the view repaints normally. Another approach
+ * would be to remove this from the JViewport
+ * class and have the JList
manage this case by using
+ * setBackingStoreEnabled
. The default is
+ * false
.
+ */
+ protected boolean scrollUnderway = false;
+
+ /*
+ * Listener that is notified each time the view changes size.
+ */
+ private ComponentListener viewListener = null;
+
+ /* Only one ChangeEvent
is needed per
+ * JViewport
instance since the
+ * event's only (read-only) state is the source property. The source
+ * of events generated here is always "this".
+ */
+ private transient ChangeEvent changeEvent = null;
+
+ /**
+ * Use graphics.copyArea
to implement scrolling.
+ * This is the fastest for most applications.
+ *
+ * @see #setScrollMode
+ * @since 1.3
+ */
+ public static final int BLIT_SCROLL_MODE = 1;
+
+ /**
+ * Draws viewport contents into an offscreen image.
+ * This was previously the default mode for JTable
.
+ * This mode may offer advantages over "blit mode"
+ * in some cases, but it requires a large chunk of extra RAM.
+ *
+ * @see #setScrollMode
+ * @since 1.3
+ */
+ public static final int BACKINGSTORE_SCROLL_MODE = 2;
+
+ /**
+ * This mode uses the very simple method of redrawing the entire
+ * contents of the scrollpane each time it is scrolled.
+ * This was the default behavior in Swing 1.0 and Swing 1.1.
+ * Either of the other two options will provide better performance
+ * in most cases.
+ *
+ * @see #setScrollMode
+ * @since 1.3
+ */
+ public static final int SIMPLE_SCROLL_MODE = 0;
+
+ /**
+ * @see #setScrollMode
+ * @since 1.3
+ */
+ private int scrollMode = BLIT_SCROLL_MODE;
+
+ //
+ // Window blitting:
+ //
+ // As mentioned in the javadoc when using windowBlit a paint event
+ // will be generated by the system if copyArea copies a non-visible
+ // portion of the view (in other words, it copies garbage). We are
+ // not guaranteed to receive the paint event before other mouse events,
+ // so we can not be sure we haven't already copied garbage a bunch of
+ // times to different parts of the view. For that reason when a blit
+ // happens and the Component is obscured (the check for obscurity
+ // is not supported on all platforms and is checked via ComponentPeer
+ // methods) the ivar repaintAll is set to true. When paint is received
+ // if repaintAll is true (we previously did a blit) it is set to
+ // false, and if the clip region is smaller than the viewport
+ // waitingForRepaint is set to true and a timer is started. When
+ // the timer fires if waitingForRepaint is true, repaint is invoked.
+ // In the mean time, if the view is asked to scroll and waitingForRepaint
+ // is true, a blit will not happen, instead the non-backing store case
+ // of scrolling will happen, which will reset waitingForRepaint.
+ // waitingForRepaint is set to false in paint when the clip rect is
+ // bigger (or equal) to the size of the viewport.
+ // A Timer is used instead of just a repaint as it appeared to offer
+ // better performance.
+
+
+ /**
+ * This is set to true in setViewPosition
+ * if doing a window blit and the viewport is obscured.
+ */
+ private transient boolean repaintAll;
+
+ /**
+ * This is set to true in paint, if repaintAll
+ * is true and the clip rectangle does not match the bounds.
+ * If true, and scrolling happens the
+ * repaint manager is not cleared which then allows for the repaint
+ * previously invoked to succeed.
+ */
+ private transient boolean waitingForRepaint;
+
+ /**
+ * Instead of directly invoking repaint, a Timer
+ * is started and when it fires, repaint is invoked.
+ */
+ private transient Timer repaintTimer;
+
+ /**
+ * Set to true in paintView when paint is invoked.
+ */
+ private transient boolean inBlitPaint;
+
+ /**
+ * Whether or not a valid view has been installed.
+ */
+ private boolean hasHadValidView;
+
+ /** Creates a JViewport
. */
+ public JViewport() {
+ super();
+ setLayout(createLayoutManager());
+ setOpaque(true);
+ updateUI();
+ setInheritsPopupMenu(true);
+ }
+
+
+
+ /**
+ * Returns the L&F object that renders this component.
+ *
+ * @return a ViewportUI
object
+ * @since 1.3
+ */
+ public ViewportUI getUI() {
+ return (ViewportUI)ui;
+ }
+
+
+ /**
+ * Sets the L&F object that renders this component.
+ *
+ * @param ui the ViewportUI
L&F object
+ * @see UIDefaults#getUI
+ * @beaninfo
+ * bound: true
+ * hidden: true
+ * attribute: visualUpdate true
+ * description: The UI object that implements the Component's LookAndFeel.
+ * @since 1.3
+ */
+ public void setUI(ViewportUI ui) {
+ super.setUI(ui);
+ }
+
+
+ /**
+ * Resets the UI property to a value from the current look and feel.
+ *
+ * @see JComponent#updateUI
+ */
+ public void updateUI() {
+ setUI((ViewportUI)UIManager.getUI(this));
+ }
+
+
+ /**
+ * Returns a string that specifies the name of the L&F class
+ * that renders this component.
+ *
+ * @return the string "ViewportUI"
+ *
+ * @see JComponent#getUIClassID
+ * @see UIDefaults#getUI
+ */
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+
+ /**
+ * Sets the JViewport
's one lightweight child,
+ * which can be null
.
+ * (Since there is only one child which occupies the entire viewport,
+ * the constraints
and index
+ * arguments are ignored.)
+ *
+ * @param child the lightweight child
of the viewport
+ * @param constraints the constraints
to be respected
+ * @param index the index
+ * @see #setView
+ */
+ protected void addImpl(Component child, Object constraints, int index) {
+ setView(child);
+ }
+
+
+ /**
+ * Removes the Viewport
s one lightweight child.
+ *
+ * @see #setView
+ */
+ public void remove(Component child) {
+ child.removeComponentListener(viewListener);
+ super.remove(child);
+ }
+
+
+ /**
+ * Scrolls the view so that Rectangle
+ * within the view becomes visible.
+ *
+ * This attempts to validate the view before scrolling if the
+ * view is currently not valid - isValid
returns false.
+ * To avoid excessive validation when the containment hierarchy is
+ * being created this will not validate if one of the ancestors does not
+ * have a peer, or there is no validate root ancestor, or one of the
+ * ancestors is not a Window
or Applet
.
+ *
+ * Note that this method will not scroll outside of the
+ * valid viewport; for example, if contentRect
is larger
+ * than the viewport, scrolling will be confined to the viewport's
+ * bounds.
+ *
+ * @param contentRect the Rectangle
to display
+ * @see JComponent#isValidateRoot
+ * @see java.awt.Component#isValid
+ * @see java.awt.Component#getPeer
+ */
+ public void scrollRectToVisible(Rectangle contentRect) {
+ Component view = getView();
+
+ if (view == null) {
+ return;
+ } else {
+ if (!view.isValid()) {
+ // If the view is not valid, validate. scrollRectToVisible
+ // may fail if the view is not valid first, contentRect
+ // could be bigger than invalid size.
+ validateView();
+ }
+ int dx = 0, dy = 0;
+
+ dx = positionAdjustment(getWidth(), contentRect.width, contentRect.x);
+ dy = positionAdjustment(getHeight(), contentRect.height, contentRect.y);
+
+ if (dx != 0 || dy != 0) {
+ Point viewPosition = getViewPosition();
+ Dimension viewSize = view.getSize();
+ int startX = viewPosition.x;
+ int startY = viewPosition.y;
+ Dimension extent = getExtentSize();
+
+ viewPosition.x -= dx;
+ viewPosition.y -= dy;
+ // Only constrain the location if the view is valid. If the
+ // the view isn't valid, it typically indicates the view
+ // isn't visible yet and most likely has a bogus size as will
+ // we, and therefore we shouldn't constrain the scrolling
+ if (view.isValid()) {
+ if (getParent().getComponentOrientation().isLeftToRight()) {
+ if (viewPosition.x + extent.width > viewSize.width) {
+ viewPosition.x = Math.max(0, viewSize.width - extent.width);
+ } else if (viewPosition.x < 0) {
+ viewPosition.x = 0;
+ }
+ } else {
+ if (extent.width > viewSize.width) {
+ viewPosition.x = viewSize.width - extent.width;
+ } else {
+ viewPosition.x = Math.max(0, Math.min(viewSize.width - extent.width, viewPosition.x));
+ }
+ }
+ if (viewPosition.y + extent.height > viewSize.height) {
+ viewPosition.y = Math.max(0, viewSize.height -
+ extent.height);
+ }
+ else if (viewPosition.y < 0) {
+ viewPosition.y = 0;
+ }
+ }
+ if (viewPosition.x != startX || viewPosition.y != startY) {
+ setViewPosition(viewPosition);
+ // NOTE: How JViewport currently works with the
+ // backing store is not foolproof. The sequence of
+ // events when setViewPosition
+ // (scrollRectToVisible) is called is to reset the
+ // views bounds, which causes a repaint on the
+ // visible region and sets an ivar indicating
+ // scrolling (scrollUnderway). When
+ // JViewport.paint is invoked if scrollUnderway is
+ // true, the backing store is blitted. This fails
+ // if between the time setViewPosition is invoked
+ // and paint is received another repaint is queued
+ // indicating part of the view is invalid. There
+ // is no way for JViewport to notice another
+ // repaint has occured and it ends up blitting
+ // what is now a dirty region and the repaint is
+ // never delivered.
+ // It just so happens JTable encounters this
+ // behavior by way of scrollRectToVisible, for
+ // this reason scrollUnderway is set to false
+ // here, which effectively disables the backing
+ // store.
+ scrollUnderway = false;
+ }
+ }
+ }
+ }
+
+ /**
+ * Ascends the Viewport
's parents stopping when
+ * a component is found that returns
+ * true
to isValidateRoot
.
+ * If all the Component
's parents are visible,
+ * validate
will then be invoked on it. The
+ * RepaintManager
is then invoked with
+ * removeInvalidComponent
. This
+ * is the synchronous version of a revalidate
.
+ */
+ private void validateView() {
+ Component validateRoot = null;
+
+ /* Find the first JComponent ancestor of this component whose
+ * isValidateRoot() method returns true.
+ */
+ for(Component c = this; c != null; c = c.getParent()) {
+ if ((c instanceof CellRendererPane) || (c.getPeer() == null)) {
+ return;
+ }
+ if ((c instanceof JComponent) &&
+ (((JComponent)c).isValidateRoot())) {
+ validateRoot = c;
+ break;
+ }
+ }
+
+ // If no validateRoot, nothing to validate from.
+ if (validateRoot == null) {
+ return;
+ }
+
+ // Make sure all ancestors are visible.
+ Component root = null;
+
+ for(Component c = validateRoot; c != null; c = c.getParent()) {
+ // We don't check isVisible here, otherwise if the component
+ // is contained in something like a JTabbedPane when the
+ // component is made visible again it won't have scrolled
+ // to the correct location.
+ if (c.getPeer() == null) {
+ return;
+ }
+ if ((c instanceof Window) || (c instanceof Applet)) {
+ root = c;
+ break;
+ }
+ }
+
+ // Make sure there is a Window ancestor.
+ if (root == null) {
+ return;
+ }
+
+ // Validate the root.
+ validateRoot.validate();
+
+ // And let the RepaintManager it does not have to validate from
+ // validateRoot anymore.
+ RepaintManager rm = RepaintManager.currentManager(this);
+
+ if (rm != null) {
+ rm.removeInvalidComponent((JComponent)validateRoot);
+ }
+ }
+
+ /* Used by the scrollRectToVisible method to determine the
+ * proper direction and amount to move by. The integer variables are named
+ * width, but this method is applicable to height also. The code assumes that
+ * parentWidth/childWidth are positive and childAt can be negative.
+ */
+ private int positionAdjustment(int parentWidth, int childWidth, int childAt) {
+
+ // +-----+
+ // | --- | No Change
+ // +-----+
+ if (childAt >= 0 && childWidth + childAt <= parentWidth) {
+ return 0;
+ }
+
+ // +-----+
+ // --------- No Change
+ // +-----+
+ if (childAt <= 0 && childWidth + childAt >= parentWidth) {
+ return 0;
+ }
+
+ // +-----+ +-----+
+ // | ---- -> | ----|
+ // +-----+ +-----+
+ if (childAt > 0 && childWidth <= parentWidth) {
+ return -childAt + parentWidth - childWidth;
+ }
+
+ // +-----+ +-----+
+ // | -------- -> |--------
+ // +-----+ +-----+
+ if (childAt >= 0 && childWidth >= parentWidth) {
+ return -childAt;
+ }
+
+ // +-----+ +-----+
+ // ---- | -> |---- |
+ // +-----+ +-----+
+ if (childAt <= 0 && childWidth <= parentWidth) {
+ return -childAt;
+ }
+
+ // +-----+ +-----+
+ //-------- | -> --------|
+ // +-----+ +-----+
+ if (childAt < 0 && childWidth >= parentWidth) {
+ return -childAt + parentWidth - childWidth;
+ }
+
+ return 0;
+ }
+
+
+ /**
+ * The viewport "scrolls" its child (called the "view") by the
+ * normal parent/child clipping (typically the view is moved in
+ * the opposite direction of the scroll). A non-null
border,
+ * or non-zero insets, isn't supported, to prevent the geometry
+ * of this component from becoming complex enough to inhibit
+ * subclassing. To create a JViewport
with a border,
+ * add it to a JPanel
that has a border.
+ *
Note: If border
is non-null
, this
+ * method will throw an exception as borders are not supported on
+ * a JViewPort
.
+ *
+ * @param border the Border
to set
+ * @exception IllegalArgumentException this method is not implemented
+ */
+ public final void setBorder(Border border) {
+ if (border != null) {
+ throw new IllegalArgumentException("JViewport.setBorder() not supported");
+ }
+ }
+
+
+ /**
+ * Returns the insets (border) dimensions as (0,0,0,0), since borders
+ * are not supported on a JViewport
.
+ *
+ * @return a Rectange
of zero dimension and zero origin
+ * @see #setBorder
+ */
+ public final Insets getInsets() {
+ return new Insets(0, 0, 0, 0);
+ }
+
+ /**
+ * Returns an Insets
object containing this
+ * JViewport
s inset values. The passed-in
+ * Insets
object will be reinitialized, and
+ * all existing values within this object are overwritten.
+ *
+ * @param insets the Insets
object which can be reused
+ * @return this viewports inset values
+ * @see #getInsets
+ * @beaninfo
+ * expert: true
+ */
+ public final Insets getInsets(Insets insets) {
+ insets.left = insets.top = insets.right = insets.bottom = 0;
+ return insets;
+ }
+
+
+ private Graphics getBackingStoreGraphics(Graphics g) {
+ Graphics bsg = backingStoreImage.getGraphics();
+ bsg.setColor(g.getColor());
+ bsg.setFont(g.getFont());
+ bsg.setClip(g.getClipBounds());
+ return bsg;
+ }
+
+
+ private void paintViaBackingStore(Graphics g) {
+ Graphics bsg = getBackingStoreGraphics(g);
+ try {
+ super.paint(bsg);
+ g.drawImage(backingStoreImage, 0, 0, this);
+ } finally {
+ bsg.dispose();
+ }
+ }
+
+ private void paintViaBackingStore(Graphics g, Rectangle oClip) {
+ Graphics bsg = getBackingStoreGraphics(g);
+ try {
+ super.paint(bsg);
+ g.setClip(oClip);
+ g.drawImage(backingStoreImage, 0, 0, this);
+ } finally {
+ bsg.dispose();
+ }
+ }
+
+ /**
+ * The JViewport
overrides the default implementation of
+ * this method (in JComponent
) to return false.
+ * This ensures
+ * that the drawing machinery will call the Viewport
's
+ * paint
+ * implementation rather than messaging the JViewport
's
+ * children directly.
+ *
+ * @return false
+ */
+ public boolean isOptimizedDrawingEnabled() {
+ return false;
+ }
+
+ /**
+ * Returns true if scroll mode is a BACKINGSTORE_SCROLL_MODE to cause
+ * painting to originate from JViewport
, or one of its
+ * ancestors. Otherwise returns false.
+ *
+ * @return true if if scroll mode is a BACKINGSTORE_SCROLL_MODE.
+ * @see JComponent#isPaintingOrigin()
+ */
+ boolean isPaintingOrigin() {
+ if (scrollMode == BACKINGSTORE_SCROLL_MODE) {
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Only used by the paint method below.
+ */
+ private Point getViewLocation() {
+ Component view = getView();
+ if (view != null) {
+ return view.getLocation();
+ }
+ else {
+ return new Point(0,0);
+ }
+ }
+
+ /**
+ * Depending on whether the backingStore
is enabled,
+ * either paint the image through the backing store or paint
+ * just the recently exposed part, using the backing store
+ * to "blit" the remainder.
+ *
+ * The term "blit" is the pronounced version of the PDP-10 + * BLT (BLock Transfer) instruction, which copied a block of + * bits. (In case you were curious.) + *+ * + * @param g the
Graphics
context within which to paint
+ */
+ public void paint(Graphics g)
+ {
+ int width = getWidth();
+ int height = getHeight();
+
+ if ((width <= 0) || (height <= 0)) {
+ return;
+ }
+
+ if (inBlitPaint) {
+ // We invoked paint as part of copyArea cleanup, let it through.
+ super.paint(g);
+ return;
+ }
+
+ if (repaintAll) {
+ repaintAll = false;
+ Rectangle clipB = g.getClipBounds();
+ if (clipB.width < getWidth() ||
+ clipB.height < getHeight()) {
+ waitingForRepaint = true;
+ if (repaintTimer == null) {
+ repaintTimer = createRepaintTimer();
+ }
+ repaintTimer.stop();
+ repaintTimer.start();
+ // We really don't need to paint, a future repaint will
+ // take care of it, but if we don't we get an ugly flicker.
+ }
+ else {
+ if (repaintTimer != null) {
+ repaintTimer.stop();
+ }
+ waitingForRepaint = false;
+ }
+ }
+ else if (waitingForRepaint) {
+ // Need a complete repaint before resetting waitingForRepaint
+ Rectangle clipB = g.getClipBounds();
+ if (clipB.width >= getWidth() &&
+ clipB.height >= getHeight()) {
+ waitingForRepaint = false;
+ repaintTimer.stop();
+ }
+ }
+
+ if (!backingStore || isBlitting() || getView() == null) {
+ super.paint(g);
+ lastPaintPosition = getViewLocation();
+ return;
+ }
+
+ // If the view is smaller than the viewport and we are not opaque
+ // (that is, we won't paint our background), we should set the
+ // clip. Otherwise, as the bounds of the view vary, we will
+ // blit garbage into the exposed areas.
+ Rectangle viewBounds = getView().getBounds();
+ if (!isOpaque()) {
+ g.clipRect(0, 0, viewBounds.width, viewBounds.height);
+ }
+
+ if (backingStoreImage == null) {
+ // Backing store is enabled but this is the first call to paint.
+ // Create the backing store, paint it and then copy to g.
+ // The backing store image will be created with the size of
+ // the viewport. We must make sure the clip region is the
+ // same size, otherwise when scrolling the backing image
+ // the region outside of the clipped region will not be painted,
+ // and result in empty areas.
+ backingStoreImage = createImage(width, height);
+ Rectangle clip = g.getClipBounds();
+ if (clip.width != width || clip.height != height) {
+ if (!isOpaque()) {
+ g.setClip(0, 0, Math.min(viewBounds.width, width),
+ Math.min(viewBounds.height, height));
+ }
+ else {
+ g.setClip(0, 0, width, height);
+ }
+ paintViaBackingStore(g, clip);
+ }
+ else {
+ paintViaBackingStore(g);
+ }
+ }
+ else {
+ if (!scrollUnderway || lastPaintPosition.equals(getViewLocation())) {
+ // No scrolling happened: repaint required area via backing store.
+ paintViaBackingStore(g);
+ } else {
+ // The image was scrolled. Manipulate the backing store and flush it to g.
+ Point blitFrom = new Point();
+ Point blitTo = new Point();
+ Dimension blitSize = new Dimension();
+ Rectangle blitPaint = new Rectangle();
+
+ Point newLocation = getViewLocation();
+ int dx = newLocation.x - lastPaintPosition.x;
+ int dy = newLocation.y - lastPaintPosition.y;
+ boolean canBlit = computeBlit(dx, dy, blitFrom, blitTo, blitSize, blitPaint);
+ if (!canBlit) {
+ // The image was either moved diagonally or
+ // moved by more than the image size: paint normally.
+ paintViaBackingStore(g);
+ } else {
+ int bdx = blitTo.x - blitFrom.x;
+ int bdy = blitTo.y - blitFrom.y;
+
+ // Move the relevant part of the backing store.
+ Rectangle clip = g.getClipBounds();
+ // We don't want to inherit the clip region when copying
+ // bits, if it is inherited it will result in not moving
+ // all of the image resulting in garbage appearing on
+ // the screen.
+ g.setClip(0, 0, width, height);
+ Graphics bsg = getBackingStoreGraphics(g);
+ try {
+ bsg.copyArea(blitFrom.x, blitFrom.y, blitSize.width, blitSize.height, bdx, bdy);
+
+ g.setClip(clip.x, clip.y, clip.width, clip.height);
+ // Paint the rest of the view; the part that has just been exposed.
+ Rectangle r = viewBounds.intersection(blitPaint);
+ bsg.setClip(r);
+ super.paint(bsg);
+
+ // Copy whole of the backing store to g.
+ g.drawImage(backingStoreImage, 0, 0, this);
+ } finally {
+ bsg.dispose();
+ }
+ }
+ }
+ }
+ lastPaintPosition = getViewLocation();
+ scrollUnderway = false;
+ }
+
+
+ /**
+ * Sets the bounds of this viewport. If the viewport's width
+ * or height has changed, fire a StateChanged
event.
+ *
+ * @param x left edge of the origin
+ * @param y top edge of the origin
+ * @param w width in pixels
+ * @param h height in pixels
+ *
+ * @see JComponent#reshape(int, int, int, int)
+ */
+ public void reshape(int x, int y, int w, int h) {
+ boolean sizeChanged = (getWidth() != w) || (getHeight() != h);
+ if (sizeChanged) {
+ backingStoreImage = null;
+ }
+ super.reshape(x, y, w, h);
+ if (sizeChanged) {
+ fireStateChanged();
+ }
+ }
+
+
+ /**
+ * Used to control the method of scrolling the viewport contents.
+ * You may want to change this mode to get maximum performance for your
+ * use case.
+ *
+ * @param mode one of the following values:
+ * scrollMode
property
+ * @see #setScrollMode
+ * @since 1.3
+ */
+ public int getScrollMode() {
+ return scrollMode;
+ }
+
+ /**
+ * Returns true
if this viewport is maintaining
+ * an offscreen image of its contents.
+ *
+ * @return true
if scrollMode
is
+ * BACKINGSTORE_SCROLL_MODE
+ *
+ * @deprecated As of Java 2 platform v1.3, replaced by
+ * getScrollMode()
.
+ */
+ @Deprecated
+ public boolean isBackingStoreEnabled() {
+ return scrollMode == BACKINGSTORE_SCROLL_MODE;
+ }
+
+
+ /**
+ * If true if this viewport will maintain an offscreen
+ * image of its contents. The image is used to reduce the cost
+ * of small one dimensional changes to the viewPosition
.
+ * Rather than repainting the entire viewport we use
+ * Graphics.copyArea
to effect some of the scroll.
+ *
+ * @param enabled if true, maintain an offscreen backing store
+ *
+ * @deprecated As of Java 2 platform v1.3, replaced by
+ * setScrollMode()
.
+ */
+ @Deprecated
+ public void setBackingStoreEnabled(boolean enabled) {
+ if (enabled) {
+ setScrollMode(BACKINGSTORE_SCROLL_MODE);
+ } else {
+ setScrollMode(BLIT_SCROLL_MODE);
+ }
+ }
+
+ private final boolean isBlitting() {
+ Component view = getView();
+ return (scrollMode == BLIT_SCROLL_MODE) &&
+ (view instanceof JComponent) && ((JComponent)view).isOpaque();
+ }
+
+
+ /**
+ * Returns the JViewport
's one child or null
.
+ *
+ * @return the viewports child, or null
if none exists
+ *
+ * @see #setView
+ */
+ public Component getView() {
+ return (getComponentCount() > 0) ? getComponent(0) : null;
+ }
+
+ /**
+ * Sets the JViewport
's one lightweight child
+ * (view
), which can be null
.
+ *
+ * @param view the viewport's new lightweight child
+ *
+ * @see #getView
+ */
+ public void setView(Component view) {
+
+ /* Remove the viewport's existing children, if any.
+ * Note that removeAll() isn't used here because it
+ * doesn't call remove() (which JViewport overrides).
+ */
+ int n = getComponentCount();
+ for(int i = n - 1; i >= 0; i--) {
+ remove(getComponent(i));
+ }
+
+ isViewSizeSet = false;
+
+ if (view != null) {
+ super.addImpl(view, null, -1);
+ viewListener = createViewListener();
+ view.addComponentListener(viewListener);
+ }
+
+ if (hasHadValidView) {
+ // Only fire a change if a view has been installed.
+ fireStateChanged();
+ }
+ else if (view != null) {
+ hasHadValidView = true;
+ }
+
+ revalidate();
+ repaint();
+ }
+
+
+ /**
+ * If the view's size hasn't been explicitly set, return the
+ * preferred size, otherwise return the view's current size.
+ * If there is no view, return 0,0.
+ *
+ * @return a Dimension
object specifying the size of the view
+ */
+ public Dimension getViewSize() {
+ Component view = getView();
+
+ if (view == null) {
+ return new Dimension(0,0);
+ }
+ else if (isViewSizeSet) {
+ return view.getSize();
+ }
+ else {
+ return view.getPreferredSize();
+ }
+ }
+
+
+ /**
+ * Sets the size of the view. A state changed event will be fired.
+ *
+ * @param newSize a Dimension
object specifying the new
+ * size of the view
+ */
+ public void setViewSize(Dimension newSize) {
+ Component view = getView();
+ if (view != null) {
+ Dimension oldSize = view.getSize();
+ if (!newSize.equals(oldSize)) {
+ // scrollUnderway will be true if this is invoked as the
+ // result of a validate and setViewPosition was previously
+ // invoked.
+ scrollUnderway = false;
+ view.setSize(newSize);
+ isViewSizeSet = true;
+ fireStateChanged();
+ }
+ }
+ }
+
+ /**
+ * Returns the view coordinates that appear in the upper left
+ * hand corner of the viewport, or 0,0 if there's no view.
+ *
+ * @return a Point
object giving the upper left coordinates
+ */
+ public Point getViewPosition() {
+ Component view = getView();
+ if (view != null) {
+ Point p = view.getLocation();
+ p.x = -p.x;
+ p.y = -p.y;
+ return p;
+ }
+ else {
+ return new Point(0,0);
+ }
+ }
+
+
+ /**
+ * Sets the view coordinates that appear in the upper left
+ * hand corner of the viewport, does nothing if there's no view.
+ *
+ * @param p a Point
object giving the upper left coordinates
+ */
+ public void setViewPosition(Point p)
+ {
+ Component view = getView();
+ if (view == null) {
+ return;
+ }
+
+ int oldX, oldY, x = p.x, y = p.y;
+
+ /* Collect the old x,y values for the views location
+ * and do the song and dance to avoid allocating
+ * a Rectangle object if we don't have to.
+ */
+ if (view instanceof JComponent) {
+ JComponent c = (JComponent)view;
+ oldX = c.getX();
+ oldY = c.getY();
+ }
+ else {
+ Rectangle r = view.getBounds();
+ oldX = r.x;
+ oldY = r.y;
+ }
+
+ /* The view scrolls in the opposite direction to mouse
+ * movement.
+ */
+ int newX = -x;
+ int newY = -y;
+
+ if ((oldX != newX) || (oldY != newY)) {
+ if (!waitingForRepaint && isBlitting() && canUseWindowBlitter()) {
+ RepaintManager rm = RepaintManager.currentManager(this);
+ // The cast to JComponent will work, if view is not
+ // a JComponent, isBlitting will return false.
+ JComponent jview = (JComponent)view;
+ Rectangle dirty = rm.getDirtyRegion(jview);
+ if (dirty == null || !dirty.contains(jview.getVisibleRect())) {
+ rm.beginPaint();
+ try {
+ Graphics g = JComponent.safelyGetGraphics(this);
+ flushViewDirtyRegion(g, dirty);
+ view.setLocation(newX, newY);
+ g.setClip(0,0,getWidth(), Math.min(getHeight(),
+ jview.getHeight()));
+ // Repaint the complete component if the blit succeeded
+ // and needsRepaintAfterBlit returns true.
+ repaintAll = (windowBlitPaint(g) &&
+ needsRepaintAfterBlit());
+ g.dispose();
+ rm.markCompletelyClean((JComponent)getParent());
+ rm.markCompletelyClean(this);
+ rm.markCompletelyClean(jview);
+ } finally {
+ rm.endPaint();
+ }
+ }
+ else {
+ // The visible region is dirty, no point in doing copyArea
+ view.setLocation(newX, newY);
+ repaintAll = false;
+ }
+ }
+ else {
+ scrollUnderway = true;
+ // This calls setBounds(), and then repaint().
+ view.setLocation(newX, newY);
+ repaintAll = false;
+ }
+ fireStateChanged();
+ }
+ }
+
+
+ /**
+ * Returns a rectangle whose origin is getViewPosition
+ * and size is getExtentSize
.
+ * This is the visible part of the view, in view coordinates.
+ *
+ * @return a Rectangle
giving the visible part of
+ * the view using view coordinates.
+ */
+ public Rectangle getViewRect() {
+ return new Rectangle(getViewPosition(), getExtentSize());
+ }
+
+
+ /**
+ * Computes the parameters for a blit where the backing store image
+ * currently contains oldLoc
in the upper left hand corner
+ * and we're scrolling to newLoc
.
+ * The parameters are modified
+ * to return the values required for the blit.
+ *
+ * @param dx the horizontal delta
+ * @param dy the vertical delta
+ * @param blitFrom the Point
we're blitting from
+ * @param blitTo the Point
we're blitting to
+ * @param blitSize the Dimension
of the area to blit
+ * @param blitPaint the area to blit
+ * @return true if the parameters are modified and we're ready to blit;
+ * false otherwise
+ */
+ protected boolean computeBlit(
+ int dx,
+ int dy,
+ Point blitFrom,
+ Point blitTo,
+ Dimension blitSize,
+ Rectangle blitPaint)
+ {
+ int dxAbs = Math.abs(dx);
+ int dyAbs = Math.abs(dy);
+ Dimension extentSize = getExtentSize();
+
+ if ((dx == 0) && (dy != 0) && (dyAbs < extentSize.height)) {
+ if (dy < 0) {
+ blitFrom.y = -dy;
+ blitTo.y = 0;
+ blitPaint.y = extentSize.height + dy;
+ }
+ else {
+ blitFrom.y = 0;
+ blitTo.y = dy;
+ blitPaint.y = 0;
+ }
+
+ blitPaint.x = blitFrom.x = blitTo.x = 0;
+
+ blitSize.width = extentSize.width;
+ blitSize.height = extentSize.height - dyAbs;
+
+ blitPaint.width = extentSize.width;
+ blitPaint.height = dyAbs;
+
+ return true;
+ }
+
+ else if ((dy == 0) && (dx != 0) && (dxAbs < extentSize.width)) {
+ if (dx < 0) {
+ blitFrom.x = -dx;
+ blitTo.x = 0;
+ blitPaint.x = extentSize.width + dx;
+ }
+ else {
+ blitFrom.x = 0;
+ blitTo.x = dx;
+ blitPaint.x = 0;
+ }
+
+ blitPaint.y = blitFrom.y = blitTo.y = 0;
+
+ blitSize.width = extentSize.width - dxAbs;
+ blitSize.height = extentSize.height;
+
+ blitPaint.width = dxAbs;
+ blitPaint.height = extentSize.height;
+
+ return true;
+ }
+
+ else {
+ return false;
+ }
+ }
+
+
+ /**
+ * Returns the size of the visible part of the view in view coordinates.
+ *
+ * @return a Dimension
object giving the size of the view
+ */
+ public Dimension getExtentSize() {
+ return getSize();
+ }
+
+
+ /**
+ * Converts a size in pixel coordinates to view coordinates.
+ * Subclasses of viewport that support "logical coordinates"
+ * will override this method.
+ *
+ * @param size a Dimension
object using pixel coordinates
+ * @return a Dimension
object converted to view coordinates
+ */
+ public Dimension toViewCoordinates(Dimension size) {
+ return new Dimension(size);
+ }
+
+ /**
+ * Converts a point in pixel coordinates to view coordinates.
+ * Subclasses of viewport that support "logical coordinates"
+ * will override this method.
+ *
+ * @param p a Point
object using pixel coordinates
+ * @return a Point
object converted to view coordinates
+ */
+ public Point toViewCoordinates(Point p) {
+ return new Point(p);
+ }
+
+
+ /**
+ * Sets the size of the visible part of the view using view coordinates.
+ *
+ * @param newExtent a Dimension
object specifying
+ * the size of the view
+ */
+ public void setExtentSize(Dimension newExtent) {
+ Dimension oldExtent = getExtentSize();
+ if (!newExtent.equals(oldExtent)) {
+ setSize(newExtent);
+ fireStateChanged();
+ }
+ }
+
+ /**
+ * A listener for the view.
+ *
+ * Warning:
+ * Serialized objects of this class will not be compatible with
+ * future Swing releases. The current serialization support is
+ * appropriate for short term storage or RMI between applications running
+ * the same version of Swing. As of 1.4, support for long term storage
+ * of all JavaBeansTM
+ * has been added to the java.beans
package.
+ * Please see {@link java.beans.XMLEncoder}.
+ */
+ protected class ViewListener extends ComponentAdapter implements Serializable
+ {
+ public void componentResized(ComponentEvent e) {
+ fireStateChanged();
+ revalidate();
+ }
+ }
+
+ /**
+ * Creates a listener for the view.
+ * @return a ViewListener
+ */
+ protected ViewListener createViewListener() {
+ return new ViewListener();
+ }
+
+
+ /**
+ * Subclassers can override this to install a different
+ * layout manager (or null
) in the constructor. Returns
+ * the LayoutManager
to install on the JViewport
.
+ *
+ * @return a LayoutManager
+ */
+ protected LayoutManager createLayoutManager() {
+ return ViewportLayout.SHARED_INSTANCE;
+ }
+
+
+ /**
+ * Adds a ChangeListener
to the list that is
+ * notified each time the view's
+ * size, position, or the viewport's extent size has changed.
+ *
+ * @param l the ChangeListener
to add
+ * @see #removeChangeListener
+ * @see #setViewPosition
+ * @see #setViewSize
+ * @see #setExtentSize
+ */
+ public void addChangeListener(ChangeListener l) {
+ listenerList.add(ChangeListener.class, l);
+ }
+
+ /**
+ * Removes a ChangeListener
from the list that's notified each
+ * time the views size, position, or the viewports extent size
+ * has changed.
+ *
+ * @param l the ChangeListener
to remove
+ * @see #addChangeListener
+ */
+ public void removeChangeListener(ChangeListener l) {
+ listenerList.remove(ChangeListener.class, l);
+ }
+
+ /**
+ * Returns an array of all the ChangeListener
s added
+ * to this JViewport with addChangeListener().
+ *
+ * @return all of the ChangeListener
s added or an empty
+ * array if no listeners have been added
+ * @since 1.4
+ */
+ public ChangeListener[] getChangeListeners() {
+ return (ChangeListener[])listenerList.getListeners(
+ ChangeListener.class);
+ }
+
+ /**
+ * Notifies all ChangeListeners
when the views
+ * size, position, or the viewports extent size has changed.
+ *
+ * @see #addChangeListener
+ * @see #removeChangeListener
+ * @see EventListenerList
+ */
+ protected void fireStateChanged()
+ {
+ Object[] listeners = listenerList.getListenerList();
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == ChangeListener.class) {
+ if (changeEvent == null) {
+ changeEvent = new ChangeEvent(this);
+ }
+ ((ChangeListener)listeners[i + 1]).stateChanged(changeEvent);
+ }
+ }
+ }
+
+ /**
+ * Always repaint in the parents coordinate system to make sure
+ * only one paint is performed by the RepaintManager
.
+ *
+ * @param tm maximum time in milliseconds before update
+ * @param x the x
coordinate (pixels over from left)
+ * @param y the y
coordinate (pixels down from top)
+ * @param w the width
+ * @param h the height
+ * @see java.awt.Component#update(java.awt.Graphics)
+ */
+ public void repaint(long tm, int x, int y, int w, int h) {
+ Container parent = getParent();
+ if(parent != null)
+ parent.repaint(tm,x+getX(),y+getY(),w,h);
+ else
+ super.repaint(tm,x,y,w,h);
+ }
+
+
+ /**
+ * Returns a string representation of this JViewport
.
+ * This method
+ * is intended to be used only for debugging purposes, and the
+ * content and format of the returned string may vary between
+ * implementations. The returned string may be empty but may not
+ * be null
.
+ *
+ * @return a string representation of this JViewport
+ */
+ protected String paramString() {
+ String isViewSizeSetString = (isViewSizeSet ?
+ "true" : "false");
+ String lastPaintPositionString = (lastPaintPosition != null ?
+ lastPaintPosition.toString() : "");
+ String scrollUnderwayString = (scrollUnderway ?
+ "true" : "false");
+
+ return super.paramString() +
+ ",isViewSizeSet=" + isViewSizeSetString +
+ ",lastPaintPosition=" + lastPaintPositionString +
+ ",scrollUnderway=" + scrollUnderwayString;
+ }
+
+ //
+ // Following is used when doBlit is true.
+ //
+
+ /**
+ * Notifies listeners of a property change. This is subclassed to update
+ * the windowBlit
property.
+ * (The putClientProperty
property is final).
+ *
+ * @param propertyName a string containing the property name
+ * @param oldValue the old value of the property
+ * @param newValue the new value of the property
+ */
+ protected void firePropertyChange(String propertyName, Object oldValue,
+ Object newValue) {
+ super.firePropertyChange(propertyName, oldValue, newValue);
+ if (propertyName.equals(EnableWindowBlit)) {
+ if (newValue != null) {
+ setScrollMode(BLIT_SCROLL_MODE);
+ } else {
+ setScrollMode(SIMPLE_SCROLL_MODE);
+ }
+ }
+ }
+
+ /**
+ * Returns true if the component needs to be completely repainted after
+ * a blit and a paint is received.
+ */
+ private boolean needsRepaintAfterBlit() {
+ // Find the first heavy weight ancestor. isObscured and
+ // canDetermineObscurity are only appropriate for heavy weights.
+ Component heavyParent = getParent();
+
+ while (heavyParent != null && heavyParent.isLightweight()) {
+ heavyParent = heavyParent.getParent();
+ }
+
+ if (heavyParent != null) {
+ ComponentPeer peer = heavyParent.getPeer();
+
+ if (peer != null && peer.canDetermineObscurity() &&
+ !peer.isObscured()) {
+ // The peer says we aren't obscured, therefore we can assume
+ // that we won't later be messaged to paint a portion that
+ // we tried to blit that wasn't valid.
+ // It is certainly possible that when we blited we were
+ // obscured, and by the time this is invoked we aren't, but the
+ // chances of that happening are pretty slim.
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private Timer createRepaintTimer() {
+ Timer timer = new Timer(300, new ActionListener() {
+ public void actionPerformed(ActionEvent ae) {
+ // waitingForRepaint will be false if a paint came down
+ // with the complete clip rect, in which case we don't
+ // have to cause a repaint.
+ if (waitingForRepaint) {
+ repaint();
+ }
+ }
+ });
+ timer.setRepeats(false);
+ return timer;
+ }
+
+ /**
+ * If the repaint manager has a dirty region for the view, the view is
+ * asked to paint.
+ *
+ * @param g the Graphics
context within which to paint
+ */
+ private void flushViewDirtyRegion(Graphics g, Rectangle dirty) {
+ JComponent view = (JComponent) getView();
+ if(dirty != null && dirty.width > 0 && dirty.height > 0) {
+ dirty.x += view.getX();
+ dirty.y += view.getY();
+ Rectangle clip = g.getClipBounds();
+ if (clip == null) {
+ // Only happens in 1.2
+ g.setClip(0, 0, getWidth(), getHeight());
+ }
+ g.clipRect(dirty.x, dirty.y, dirty.width, dirty.height);
+ clip = g.getClipBounds();
+ // Only paint the dirty region if it is visible.
+ if (clip.width > 0 && clip.height > 0) {
+ paintView(g);
+ }
+ }
+ }
+
+ /**
+ * Used when blitting.
+ *
+ * @param g the Graphics
context within which to paint
+ * @return true if blitting succeeded; otherwise false
+ */
+ private boolean windowBlitPaint(Graphics g) {
+ int width = getWidth();
+ int height = getHeight();
+
+ if ((width == 0) || (height == 0)) {
+ return false;
+ }
+
+ boolean retValue;
+ RepaintManager rm = RepaintManager.currentManager(this);
+ JComponent view = (JComponent) getView();
+
+ if (lastPaintPosition == null ||
+ lastPaintPosition.equals(getViewLocation())) {
+ paintView(g);
+ retValue = false;
+ } else {
+ // The image was scrolled. Manipulate the backing store and flush
+ // it to g.
+ Point blitFrom = new Point();
+ Point blitTo = new Point();
+ Dimension blitSize = new Dimension();
+ Rectangle blitPaint = new Rectangle();
+
+ Point newLocation = getViewLocation();
+ int dx = newLocation.x - lastPaintPosition.x;
+ int dy = newLocation.y - lastPaintPosition.y;
+ boolean canBlit = computeBlit(dx, dy, blitFrom, blitTo, blitSize,
+ blitPaint);
+ if (!canBlit) {
+ paintView(g);
+ retValue = false;
+ } else {
+ // Prepare the rest of the view; the part that has just been
+ // exposed.
+ Rectangle r = view.getBounds().intersection(blitPaint);
+ r.x -= view.getX();
+ r.y -= view.getY();
+
+ blitDoubleBuffered(view, g, r.x, r.y, r.width, r.height,
+ blitFrom.x, blitFrom.y, blitTo.x, blitTo.y,
+ blitSize.width, blitSize.height);
+ retValue = true;
+ }
+ }
+ lastPaintPosition = getViewLocation();
+ return retValue;
+ }
+
+ //
+ // NOTE: the code below uses paintForceDoubleBuffered for historical
+ // reasons. If we're going to allow a blit we've already accounted for
+ // everything that paintImmediately and _paintImmediately does, for that
+ // reason we call into paintForceDoubleBuffered to diregard whether or
+ // not setDoubleBuffered(true) was invoked on the view.
+ //
+
+ private void blitDoubleBuffered(JComponent view, Graphics g,
+ int clipX, int clipY, int clipW, int clipH,
+ int blitFromX, int blitFromY, int blitToX, int blitToY,
+ int blitW, int blitH) {
+ // NOTE:
+ // blitFrom/blitTo are in JViewport coordinates system
+ // not the views coordinate space.
+ // clip* are in the views coordinate space.
+ RepaintManager rm = RepaintManager.currentManager(this);
+ int bdx = blitToX - blitFromX;
+ int bdy = blitToY - blitFromY;
+
+ // Shift the scrolled region
+ rm.copyArea(this, g, blitFromX, blitFromY, blitW, blitH, bdx, bdy,
+ false);
+
+ // Paint the newly exposed region.
+ int x = view.getX();
+ int y = view.getY();
+ g.translate(x, y);
+ g.setClip(clipX, clipY, clipW, clipH);
+ view.paintForceDoubleBuffered(g);
+ g.translate(-x, -y);
+ }
+
+ /**
+ * Called to paint the view, usually when blitPaint
+ * can not blit.
+ *
+ * @param g the Graphics
context within which to paint
+ */
+ private void paintView(Graphics g) {
+ Rectangle clip = g.getClipBounds();
+ JComponent view = (JComponent)getView();
+
+ if (view.getWidth() >= getWidth()) {
+ // Graphics is relative to JViewport, need to map to view's
+ // coordinates space.
+ int x = view.getX();
+ int y = view.getY();
+ g.translate(x, y);
+ g.setClip(clip.x - x, clip.y - y, clip.width, clip.height);
+ view.paintForceDoubleBuffered(g);
+ g.translate(-x, -y);
+ g.setClip(clip.x, clip.y, clip.width, clip.height);
+ }
+ else {
+ // To avoid any problems that may result from the viewport being
+ // bigger than the view we start painting from the viewport.
+ try {
+ inBlitPaint = true;
+ paintForceDoubleBuffered(g);
+ } finally {
+ inBlitPaint = false;
+ }
+ }
+ }
+
+ /**
+ * Returns true if the viewport is not obscured by one of its ancestors,
+ * or its ancestors children and if the viewport is showing. Blitting
+ * when the view isn't showing will work,
+ * or rather copyArea
will work,
+ * but will not produce the expected behavior.
+ */
+ private boolean canUseWindowBlitter() {
+ if (!isShowing() || (!(getParent() instanceof JComponent) &&
+ !(getView() instanceof JComponent))) {
+ return false;
+ }
+ if (isPainting()) {
+ // We're in the process of painting, don't blit. If we were
+ // to blit we would draw on top of what we're already drawing,
+ // so bail.
+ return false;
+ }
+
+ Rectangle dirtyRegion = RepaintManager.currentManager(this).
+ getDirtyRegion((JComponent)getParent());
+
+ if (dirtyRegion != null && dirtyRegion.width > 0 &&
+ dirtyRegion.height > 0) {
+ // Part of the scrollpane needs to be repainted too, don't blit.
+ return false;
+ }
+
+ Rectangle clip = new Rectangle(0,0,getWidth(),getHeight());
+ Rectangle oldClip = new Rectangle();
+ Rectangle tmp2 = null;
+ Container parent;
+ Component lastParent = null;
+ int x, y, w, h;
+
+ for(parent = this; parent != null && isLightweightComponent(parent); parent = parent.getParent()) {
+ x = parent.getX();
+ y = parent.getY();
+ w = parent.getWidth();
+ h = parent.getHeight();
+
+ oldClip.setBounds(clip);
+ SwingUtilities.computeIntersection(0, 0, w, h, clip);
+ if(!clip.equals(oldClip))
+ return false;
+
+ if(lastParent != null && parent instanceof JComponent &&
+ !((JComponent)parent).isOptimizedDrawingEnabled()) {
+ Component comps[] = parent.getComponents();
+ int index = 0;
+
+ for(int i = comps.length - 1 ;i >= 0; i--) {
+ if(comps[i] == lastParent) {
+ index = i - 1;
+ break;
+ }
+ }
+
+ while(index >= 0) {
+ tmp2 = comps[index].getBounds(tmp2);
+
+ if(tmp2.intersects(clip))
+ return false;
+ index--;
+ }
+ }
+ clip.x += x;
+ clip.y += y;
+ lastParent = parent;
+ }
+ if (parent == null) {
+ // No Window parent.
+ return false;
+ }
+ return true;
+ }
+
+
+/////////////////
+// Accessibility support
+////////////////
+
+ /**
+ * Gets the AccessibleContext associated with this JViewport.
+ * For viewports, the AccessibleContext takes the form of an
+ * AccessibleJViewport.
+ * A new AccessibleJViewport instance is created if necessary.
+ *
+ * @return an AccessibleJViewport that serves as the
+ * AccessibleContext of this JViewport
+ */
+ public AccessibleContext getAccessibleContext() {
+ if (accessibleContext == null) {
+ accessibleContext = new AccessibleJViewport();
+ }
+ return accessibleContext;
+ }
+
+ /**
+ * This class implements accessibility support for the
+ * JViewport
class. It provides an implementation of the
+ * Java Accessibility API appropriate to viewport user-interface elements.
+ *
+ * Warning:
+ * Serialized objects of this class will not be compatible with
+ * future Swing releases. The current serialization support is
+ * appropriate for short term storage or RMI between applications running
+ * the same version of Swing. As of 1.4, support for long term storage
+ * of all JavaBeansTM
+ * has been added to the java.beans
package.
+ * Please see {@link java.beans.XMLEncoder}.
+ */
+ protected class AccessibleJViewport extends AccessibleJComponent {
+ /**
+ * Get the role of this object.
+ *
+ * @return an instance of AccessibleRole describing the role of
+ * the object
+ */
+ public AccessibleRole getAccessibleRole() {
+ return AccessibleRole.VIEWPORT;
+ }
+ } // inner class AccessibleJViewport
+}