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: + *

  1. The view and parent view and checked to see if they are + * JComponents, + * if they aren't, stop and repaint the whole viewport. + *
  2. If the viewport is obscured by an ancestor, stop and repaint the whole + * viewport. + *
  3. Compute the region that will become visible, if it is as big as + * the viewport, stop and repaint the whole view region. + *
  4. Obtain the ancestor Window's graphics and + * do a copyArea on the scrolled region. + *
  5. Message the view to repaint the newly visible region. + *
  6. The next time paint is invoked on the viewport, if the clip region + * is smaller than the viewport size a timer is kicked off to repaint the + * whole region. + *
+ * In general this approach is much faster. Compared to the backing store + * approach this avoids the overhead of maintaining an offscreen buffer and + * having to do two copyAreas. + * 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 Viewports 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 + * JViewports 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: + * + * + * @see #BLIT_SCROLL_MODE + * @see #BACKINGSTORE_SCROLL_MODE + * @see #SIMPLE_SCROLL_MODE + * + * @beaninfo + * bound: false + * description: Method of moving contents for incremental scrolls. + * enum: BLIT_SCROLL_MODE JViewport.BLIT_SCROLL_MODE + * BACKINGSTORE_SCROLL_MODE JViewport.BACKINGSTORE_SCROLL_MODE + * SIMPLE_SCROLL_MODE JViewport.SIMPLE_SCROLL_MODE + * + * @since 1.3 + */ + public void setScrollMode(int mode) { + scrollMode = mode; + if (mode == BACKINGSTORE_SCROLL_MODE) { + backingStore = true; + } else { + backingStore = false; + } + } + + /** + * Returns the current scrolling mode. + * + * @return the 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 ChangeListeners added + * to this JViewport with addChangeListener(). + * + * @return all of the ChangeListeners 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 +}