Index: javax/swing/JViewport.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/JViewport.java,v retrieving revision 1.27 diff -u -r1.27 JViewport.java --- javax/swing/JViewport.java 12 Sep 2005 20:59:15 -0000 1.27 +++ javax/swing/JViewport.java 22 Sep 2005 14:34:42 -0000 @@ -41,6 +41,7 @@ import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; +import java.awt.Image; import java.awt.Insets; import java.awt.LayoutManager; import java.awt.Point; @@ -181,19 +182,37 @@ } } - private static final long serialVersionUID = -6925142919680527970L; - public static final int SIMPLE_SCROLL_MODE = 0; public static final int BLIT_SCROLL_MODE = 1; public static final int BACKINGSTORE_SCROLL_MODE = 2; + private static final long serialVersionUID = -6925142919680527970L; + + protected boolean scrollUnderway; + protected boolean isViewSizeSet; + + /** + * This flag indicates whether we use a backing store for drawing. + * + * @deprecated since JDK 1.3 + */ + protected boolean backingStore; + + /** + * The backingstore image used for the backingstore and blit scroll methods. + */ + protected Image backingStoreImage; + + /** + * The position at which the view has been drawn the last time. This is used + * to determine the bittable area. + */ + protected Point lastPaintPosition; + ChangeEvent changeEvent = new ChangeEvent(this); int scrollMode; - protected boolean scrollUnderway; - protected boolean isViewSizeSet; - /** * The width and height of the Viewport's area in terms of view * coordinates. Typically this will be the same as the width and height @@ -201,27 +220,45 @@ * width and height, which it may do, for example if it magnifies or * rotates its view. * - * @see #toViewCoordinates + * @see #toViewCoordinates(Dimension) */ Dimension extentSize; /** * The width and height of the view in its own coordinate space. */ - Dimension viewSize; - Point lastPaintPosition; - /** * The ViewListener instance. */ ViewListener viewListener; /** - * The accessible context of this JViewport. + * Stores the location from where to blit. This is a cached Point object used + * in blitting calculations. */ - AccessibleContext accessibleContext; + Point cachedBlitFrom; + + /** + * Stores the location where to blit to. This is a cached Point object used + * in blitting calculations. + */ + Point cachedBlitTo; + + /** + * Stores the width of the blitted area. This is a cached Dimension object + * used in blitting calculations. + */ + Dimension cachedBlitSize; + + /** + * Stores the bounds of the area that needs to be repainted. This is a cached + * Rectangle object used in blitting calculations. + */ + Rectangle cachedBlitPaint; + + boolean damaged = true; public JViewport() { @@ -229,6 +266,11 @@ setScrollMode(BLIT_SCROLL_MODE); setLayout(createLayoutManager()); updateUI(); + lastPaintPosition = new Point(); + cachedBlitFrom = new Point(); + cachedBlitTo = new Point(); + cachedBlitSize = new Dimension(); + cachedBlitPaint = new Rectangle(); } public Dimension getExtentSize() @@ -322,6 +364,7 @@ isViewSizeSet = false; fireStateChanged(); } + repaint(); } public Rectangle getViewRect() @@ -390,12 +433,14 @@ public void revalidate() { + damaged = true; fireStateChanged(); super.revalidate(); } public void reshape(int x, int y, int w, int h) { + damaged = true; boolean changed = (x != getX()) || (y != getY()) @@ -406,14 +451,6 @@ fireStateChanged(); } - protected void addImpl(Component comp, Object constraints, int index) - { - if (getComponentCount() > 0) - remove(getComponents()[0]); - - super.addImpl(comp, constraints, index); - } - public final Insets getInsets() { return new Insets(0,0,0,0); @@ -437,7 +474,36 @@ public void paint(Graphics g) { - paintComponent(g); + Component view = getView(); + + if (view == null) + return; + + Point pos = getViewPosition(); + Rectangle viewBounds = view.getBounds(); + Rectangle portBounds = getBounds(); + + if (viewBounds.width == 0 + || viewBounds.height == 0 + || portBounds.width == 0 + || portBounds.height == 0) + return; + + switch (getScrollMode()) + { + + case JViewport.BACKINGSTORE_SCROLL_MODE: + paintBackingStore(g); + break; + case JViewport.BLIT_SCROLL_MODE: + paintBlit(g); + break; + case JViewport.SIMPLE_SCROLL_MODE: + default: + paintSimple(g); + break; + } + damaged = false; } public void addChangeListener(ChangeListener listener) @@ -455,13 +521,6 @@ return (ChangeListener[]) getListeners(ChangeListener.class); } - protected void fireStateChanged() - { - ChangeListener[] listeners = getChangeListeners(); - for (int i = 0; i < listeners.length; ++i) - listeners[i].stateChanged(changeEvent); - } - /** * This method returns the String ID of the UI class of Separator. * @@ -507,28 +566,6 @@ } /** - * Creates a address@hidden ViewListener} that is supposed to listen for - * size changes on the view component. - * - * @return a ViewListener instance - */ - protected ViewListener createViewListener() - { - return new ViewListener(); - } - - /** - * Creates the LayoutManager that is used for this viewport. Override - * this method if you want to use a custom LayoutManager. - * - * @return a LayoutManager to use for this viewport - */ - protected LayoutManager createLayoutManager() - { - return new ViewportLayout(); - } - - /** * Scrolls the view so that contentRect becomes visible. * * @param contentRect the rectangle to make visible within the view @@ -580,5 +617,256 @@ if (accessibleContext == null) accessibleContext = new AccessibleJViewport(); return accessibleContext; + } + + protected void addImpl(Component comp, Object constraints, int index) + { + if (getComponentCount() > 0) + remove(getComponents()[0]); + + super.addImpl(comp, constraints, index); + } + + protected void fireStateChanged() + { + ChangeListener[] listeners = getChangeListeners(); + for (int i = 0; i < listeners.length; ++i) + listeners[i].stateChanged(changeEvent); + } + + /** + * Creates a address@hidden ViewListener} that is supposed to listen for + * size changes on the view component. + * + * @return a ViewListener instance + */ + protected ViewListener createViewListener() + { + return new ViewListener(); + } + + /** + * Creates the LayoutManager that is used for this viewport. Override + * this method if you want to use a custom LayoutManager. + * + * @return a LayoutManager to use for this viewport + */ + protected LayoutManager createLayoutManager() + { + return new ViewportLayout(); + } + + /** + * Computes the parameters for the blitting scroll method. dx + * and dy specifiy the X and Y offset by which the viewport + * is scrolled. All other arguments are output parameters and are filled by + * this method. + * + * blitFrom holds the position of the blit rectangle in the + * viewport rectangle before scrolling, blitTo where the blitArea + * is copied to. + * + * blitSize holds the size of the blit area and + * blitPaint is the area of the view that needs to be painted. + * + * This method returns true if blitting is possible and + * false if the viewport has to be repainted completetly without + * blitting. + * + * @param dx the horizontal delta + * @param dy the vertical delta + * @param blitFrom the position from where to blit; set by this method + * @param blitTo the position where to blit area is copied to; set by this + * method + * @param blitSize the size of the blitted area; set by this method + * @param blitPaint the area that needs repainting; set by this method + * + * @return true if blitting is possible, + * false otherwise + */ + protected boolean computeBlit(int dx, int dy, Point blitFrom, Point blitTo, + Dimension blitSize, Rectangle blitPaint) + { + if ((dx != 0 && dy != 0) || damaged) + // We cannot blit if the viewport is scrolled in both directions at + // once. + return false; + + Rectangle portBounds = SwingUtilities.calculateInnerArea(this, getBounds()); + + // Compute the blitFrom and blitTo parameters. + blitFrom.x = portBounds.x; + blitFrom.y = portBounds.y; + blitTo.x = portBounds.x; + blitTo.y = portBounds.y; + + if (dy > 0) + { + blitFrom.y = portBounds.y + dy; + } + else if (dy < 0) + { + blitTo.y = portBounds.y - dy; + } + else if (dx > 0) + { + blitFrom.x = portBounds.x + dx; + } + else if (dx < 0) + { + blitTo.x = portBounds.x - dx; + } + + // Compute size of the blit area. + if (dx != 0) + { + blitSize.width = portBounds.width - Math.abs(dx); + blitSize.height = portBounds.height; + } + else if (dy != 0) + { + blitSize.width = portBounds.width; + blitSize.height = portBounds.height - Math.abs(dy); + } + + // Compute the blitPaint parameter. + blitPaint.setBounds(portBounds); + if (dy > 0) + { + blitPaint.y = portBounds.y + portBounds.height - dy; + blitPaint.height = dy; + } + else if (dy < 0) + { + blitPaint.height = -dy; + } + if (dx > 0) + { + blitPaint.x = portBounds.x + portBounds.width - dx; + blitPaint.width = dx; + } + else if (dx < 0) + { + blitPaint.width = -dx; + } + + return true; + } + + /** + * Paints the viewport in case we have a scrollmode of + * address@hidden #SIMPLE_SCROLL_MODE}. + * + * This simply paints the view directly on the surface of the viewport. + * + * @param g the graphics context to use + */ + void paintSimple(Graphics g) + { + Point pos = getViewPosition(); + Component view = getView(); + boolean translated = false; + try + { + g.translate(-pos.x, -pos.y); + translated = true; + view.paint(g); + } + finally + { + if (translated) + g.translate (pos.x, pos.y); + } + } + + /** + * Paints the viewport in case we have a scroll mode of + * address@hidden #BACKINGSTORE_SCROLL_MODE}. + * + * This method uses a backing store image to paint the view to, which is then + * subsequently painted on the screen. This should make scrolling more + * smooth. + * + * @param g the graphics context to use + */ + void paintBackingStore(Graphics g) + { + // If we have no backing store image yet or the size of the component has + // changed, we need to rebuild the backing store. + if (backingStoreImage == null || damaged) + { + backingStoreImage = createImage(getWidth(), getHeight()); + Graphics g2 = backingStoreImage.getGraphics(); + paintSimple(g2); + g2.dispose(); + } + // Otherwise we can perform the blitting on the backing store image: + // First we move the part that remains visible after scrolling, then + // we only need to paint the bit that becomes newly visible. + else + { + Graphics g2 = backingStoreImage.getGraphics(); + Point viewPosition = getViewPosition(); + int dx = viewPosition.x - lastPaintPosition.x; + int dy = viewPosition.y - lastPaintPosition.y; + boolean canBlit = computeBlit(dx, dy, cachedBlitFrom, cachedBlitTo, + cachedBlitSize, cachedBlitPaint); + if (canBlit) + { + // Copy the part that remains visible during scrolling. + g2.copyArea(cachedBlitFrom.x, cachedBlitFrom.y, + cachedBlitSize.width, cachedBlitSize.height, + cachedBlitTo.x - cachedBlitFrom.x, + cachedBlitTo.y - cachedBlitFrom.y); + // Now paint the part that becomes newly visible. + g2.setClip(cachedBlitPaint.x, cachedBlitPaint.y, + cachedBlitPaint.width, cachedBlitPaint.height); + paintSimple(g2); + } + // If blitting is not possible for some reason, fall back to repainting + // everything. + else + { + paintSimple(g2); + } + g2.dispose(); + } + // Actually draw the backingstore image to the graphics context. + g.drawImage(backingStoreImage, 0, 0, this); + // Update the lastPaintPosition so that we know what is already drawn when + // we paint the next time. + lastPaintPosition.setLocation(getViewPosition()); + } + + /** + * Paints the viewport in case we have a scrollmode of + * address@hidden #BLIT_SCROLL_MODE}. + * + * This paints the viewport using a backingstore and a blitting algorithm. + * Only the newly exposed area of the view is painted from the view painting + * methods, the remainder is copied from the backing store. + * + * @param g the graphics context to use + */ + void paintBlit(Graphics g) + { + // We cannot perform blitted painting as it is described in Sun's API docs. + // There it is suggested that this painting method should blit directly + // on the parent window's surface. This is not possible because when using + // Swing's double buffering (at least our implementation), it would + // immediatly be painted when the buffer is painted on the screen. For this + // to work we would need a kind of hole in the buffer image. And honestly + // I find this method not very elegant. + // The alternative, blitting directly on the buffer image, is also not + // possible because the buffer image gets cleared everytime when an opaque + // parent component is drawn on it. + + // What we do instead is falling back to the backing store approach which + // is in fact a mixed blitting/backing store approach where the blitting + // is performed on the backing store image and this is then drawn to the + // graphics context. This is very robust and works independent of the + // painting mechanism that is used by Swing. And it should have comparable + // performance characteristics as the blitting method. + paintBackingStore(g); } } Index: javax/swing/plaf/basic/BasicViewportUI.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/basic/BasicViewportUI.java,v retrieving revision 1.17 diff -u -r1.17 BasicViewportUI.java --- javax/swing/plaf/basic/BasicViewportUI.java 11 Aug 2005 19:59:09 -0000 1.17 +++ javax/swing/plaf/basic/BasicViewportUI.java 22 Sep 2005 14:34:42 -0000 @@ -38,61 +38,20 @@ package javax.swing.plaf.basic; -import java.awt.Component; -import java.awt.Dimension; -import java.awt.Graphics; -import java.awt.Image; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.image.ImageObserver; - import javax.swing.JComponent; -import javax.swing.JViewport; -import javax.swing.ViewportLayout; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; +import javax.swing.UIManager; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ViewportUI; public class BasicViewportUI extends ViewportUI { - - ChangeListener changeListener; - Image backingStoreImage; - int backingStoreWidth = -1; - int backingStoreHeight = -1; - - class ChangeHandler implements ChangeListener + protected void installDefaults(JComponent c) { - public void stateChanged(ChangeEvent event) - { - JViewport v = (JViewport) event.getSource(); - v.repaint(); - } - } - - void installDefaults(JComponent c) - { c.setOpaque(true); + c.setBackground(UIManager.getColor("Viewport.background")); } - - void uninstallDefaults(JComponent c) - { - } - - void installListeners(JComponent c) + protected void uninstallDefaults(JComponent c) { - ((JViewport)c).addChangeListener(changeListener); - } - - void uninstallListeners(JComponent c) - { - ((JViewport)c).removeChangeListener(changeListener); - } - - public BasicViewportUI() - { - changeListener = new ChangeHandler(); } public static ComponentUI createUI(JComponent c) @@ -103,132 +62,12 @@ public void installUI(JComponent c) { super.installUI(c); - installListeners(c); + installDefaults(c); } public void uninstallUI(JComponent c) { - uninstallListeners(c); - } - - - public Dimension getPreferredSize(JComponent c) - { - // let the ViewportLayout decide - return null; - } - - public void paint(Graphics g, JComponent c) - { - JViewport port = (JViewport)c; - Component view = port.getView(); - - if (view == null) - return; - - Point pos = port.getViewPosition(); - Rectangle viewBounds = view.getBounds(); - Rectangle portBounds = port.getBounds(); - - if (viewBounds.width == 0 - || viewBounds.height == 0 - || portBounds.width == 0 - || portBounds.height == 0) - return; - - switch (port.getScrollMode()) - { - - case JViewport.BACKINGSTORE_SCROLL_MODE: - paintBackingStore(g, port, view, pos, viewBounds, portBounds); - break; - - case JViewport.BLIT_SCROLL_MODE: - // FIXME: implement separate blit mode - - case JViewport.SIMPLE_SCROLL_MODE: - default: - paintSimple(g, port, view, pos, viewBounds, portBounds); - break; - } - } - - private void paintSimple(Graphics g, - JViewport v, - Component view, - Point pos, - Rectangle viewBounds, - Rectangle portBounds) - { - Rectangle oldClip = g.getClipBounds(); - g.setClip(new Rectangle(0, 0, portBounds.width, portBounds.height)); - g.translate (-pos.x, -pos.y); - try - { - view.paint(g); - } - finally - { - g.translate (pos.x, pos.y); - g.setClip (oldClip); - } - } - - private void paintBackingStore(Graphics g, - JViewport v, - Component view, - Point pos, - Rectangle viewBounds, - Rectangle portBounds) - { - if (backingStoreImage == null - || backingStoreWidth != viewBounds.width - || backingStoreHeight != viewBounds.height) - { - backingStoreImage = v.createImage(viewBounds.width, viewBounds.height); - backingStoreWidth = viewBounds.width; - backingStoreHeight = viewBounds.height; - } - - Graphics g2 = backingStoreImage.getGraphics(); - - if (v.getBackground() != null) - { - // fill the backing store background - java.awt.Color save = g2.getColor(); - g2.setColor(v.getBackground()); - g2.fillRect (0, 0, backingStoreWidth, backingStoreHeight); - g2.setColor(save); - - // fill the viewport background - save = g.getColor(); - g.setColor(v.getBackground()); - g.fillRect (0, 0, portBounds.width, portBounds.height); - g.setColor(save); - - } - else - { - // clear the backing store background - g2.clearRect(0, 0, backingStoreWidth, backingStoreHeight); - - // clear the viewport background - g.clearRect(0, 0, portBounds.width, portBounds.height); - } - - g2.setClip(g.getClipBounds()); - g2.translate(-pos.x, -pos.y); - try - { - view.paint(g2); - } - finally - { - g2.translate(pos.x, pos.y); - } - g2 = null; - g.drawImage(backingStoreImage, - 0, 0, - (ImageObserver)null); + super.uninstallUI(c); + uninstallDefaults(c); } }