aboutsummaryrefslogtreecommitdiff
path: root/libjava/classpath/javax/swing/JViewport.java
diff options
context:
space:
mode:
Diffstat (limited to 'libjava/classpath/javax/swing/JViewport.java')
-rw-r--r--libjava/classpath/javax/swing/JViewport.java579
1 files changed, 465 insertions, 114 deletions
diff --git a/libjava/classpath/javax/swing/JViewport.java b/libjava/classpath/javax/swing/JViewport.java
index d750bad29cc..5f20f0aa60a 100644
--- a/libjava/classpath/javax/swing/JViewport.java
+++ b/libjava/classpath/javax/swing/JViewport.java
@@ -41,6 +41,7 @@ package javax.swing;
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;
@@ -49,6 +50,9 @@ import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.io.Serializable;
+import javax.accessibility.Accessible;
+import javax.accessibility.AccessibleContext;
+import javax.accessibility.AccessibleRole;
import javax.swing.border.Border;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
@@ -95,17 +99,41 @@ import javax.swing.plaf.ViewportUI;
* the underlying child at position <code>(-VX,-VY)</code></p>
*
*/
-public class JViewport extends JComponent
+public class JViewport extends JComponent implements Accessible
{
+ /**
+ * Provides accessibility support for <code>JViewport</code>.
+ *
+ * @author Roman Kennke (roman@kennke.org)
+ */
+ protected class AccessibleJViewport extends AccessibleJComponent
+ {
+ /**
+ * Creates a new instance of <code>AccessibleJViewport</code>.
+ */
+ public AccessibleJViewport()
+ {
+ // Nothing to do here.
+ }
+
+ /**
+ * Returns the accessible role of <code>JViewport</code>, which is
+ * {@link AccessibleRole#VIEWPORT}.
+ *
+ * @return the accessible role of <code>JViewport</code>
+ */
+ public AccessibleRole getAccessibleRole()
+ {
+ return AccessibleRole.VIEWPORT;
+ }
+ }
/**
* A {@link java.awt.event.ComponentListener} that listens for
- * changes of the view's size. This class forbids changes of the view
- * component's size that would exceed the viewport's size.
+ * changes of the view's size. This triggers a revalidate() call on the
+ * viewport.
*/
- protected class ViewListener
- extends ComponentAdapter
- implements Serializable
+ protected class ViewListener extends ComponentAdapter implements Serializable
{
private static final long serialVersionUID = -2812489404285958070L;
@@ -114,57 +142,53 @@ public class JViewport extends JComponent
*/
protected ViewListener()
{
+ // Nothing to do here.
}
/**
* Receives notification when a component (in this case: the view
- * component) changes it's size.
+ * component) changes it's size. This simply triggers a revalidate() on the
+ * viewport.
*
* @param ev the ComponentEvent describing the change
*/
public void componentResized(ComponentEvent ev)
{
- // According to some tests that I did with Sun's implementation
- // this class is supposed to make sure that the view component
- // is not resized to a larger size than the viewport.
- // This is not documented anywhere. What I did is: I subclassed JViewport
- // and ViewListener and 'disabled' the componentResized method by
- // overriding it and not calling super.componentResized().
- // When this method is disabled I can set the size on the view component
- // normally, when it is enabled, it gets immediatly resized back,
- // after a resize attempt that would exceed the Viewport's size.
- Component comp = ev.getComponent();
- Dimension newSize = comp.getSize();
- Dimension viewportSize = getSize();
- boolean revert = false;
- if (newSize.width > viewportSize.width)
- {
- newSize.width = viewportSize.width;
- revert = true;
- }
- if (newSize.height > viewportSize.height)
- {
- newSize.height = viewportSize.height;
- revert = true;
- }
- if (revert == true)
- comp.setSize(newSize);
+ revalidate();
}
}
- 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
@@ -172,29 +196,75 @@ public class JViewport extends JComponent
* 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;
+ /**
+ * Stores the location from where to blit. This is a cached Point object used
+ * in blitting calculations.
+ */
+ Point cachedBlitFrom;
+
+ /**
+ * Stores the location where to blit to. This is a cached Point object used
+ * in blitting calculations.
+ */
+ Point cachedBlitTo;
+
+ /**
+ * Stores the width of the blitted area. This is a cached Dimension object
+ * used in blitting calculations.
+ */
+ Dimension cachedBlitSize;
+
+ /**
+ * Stores the bounds of the area that needs to be repainted. This is a cached
+ * Rectangle object used in blitting calculations.
+ */
+ Rectangle cachedBlitPaint;
+
+ boolean damaged = true;
+
+ /**
+ * A flag indicating if the size of the viewport has changed since the
+ * last repaint. This is used in double buffered painting to check if we
+ * need a new double buffer, or can reuse the old one.
+ */
+ boolean sizeChanged = true;
+
public JViewport()
{
setOpaque(true);
- setScrollMode(BLIT_SCROLL_MODE);
- setLayout(createLayoutManager());
+ String scrollModeProp =
+ System.getProperty("gnu.javax.swing.JViewport.scrollMode",
+ "BLIT");
+ int myScrollMode;
+ if (scrollModeProp.equalsIgnoreCase("simple"))
+ myScrollMode = SIMPLE_SCROLL_MODE;
+ else if (scrollModeProp.equalsIgnoreCase("backingstore"))
+ myScrollMode = BACKINGSTORE_SCROLL_MODE;
+ else
+ myScrollMode = BLIT_SCROLL_MODE;
+ setScrollMode(myScrollMode);
+
updateUI();
+ setLayout(createLayoutManager());
+ lastPaintPosition = new Point();
+ cachedBlitFrom = new Point();
+ cachedBlitTo = new Point();
+ cachedBlitSize = new Dimension();
+ cachedBlitPaint = new Rectangle();
}
public Dimension getExtentSize()
@@ -248,9 +318,14 @@ public class JViewport extends JComponent
viewSize = newSize;
Component view = getView();
if (view != null)
- view.setSize(viewSize);
+ {
+ if (newSize != view.getSize())
+ {
+ view.setSize(viewSize);
+ fireStateChanged();
+ }
+ }
isViewSizeSet = true;
- fireStateChanged();
}
/**
@@ -275,13 +350,17 @@ public class JViewport extends JComponent
public void setViewPosition(Point p)
{
+ if (getViewPosition().equals(p))
+ return;
Component view = getView();
if (view != null)
{
Point q = new Point(-p.x, -p.y);
view.setLocation(q);
+ isViewSizeSet = false;
fireStateChanged();
}
+ repaint();
}
public Rectangle getViewRect()
@@ -331,12 +410,8 @@ public class JViewport extends JComponent
public void setView(Component v)
{
- while (getComponentCount() > 0)
- {
- if (viewListener != null)
- getView().removeComponentListener(viewListener);
- remove(0);
- }
+ if (viewListener != null)
+ getView().removeComponentListener(viewListener);
if (v != null)
{
@@ -346,37 +421,25 @@ public class JViewport extends JComponent
add(v);
fireStateChanged();
}
- }
-
- public void revalidate()
- {
- fireStateChanged();
- super.revalidate();
+ revalidate();
+ repaint();
}
public void reshape(int x, int y, int w, int h)
{
- boolean changed =
- (x != getX())
- || (y != getY())
- || (w != getWidth())
- || (h != getHeight());
+ if (w != getWidth() || h != getHeight())
+ sizeChanged = true;
super.reshape(x, y, w, h);
- if (changed)
- fireStateChanged();
- }
-
- protected void addImpl(Component comp, Object constraints, int index)
- {
- if (getComponentCount() > 0)
- remove(getComponents()[0]);
-
- super.addImpl(comp, constraints, index);
+ if (sizeChanged)
+ {
+ damaged = true;
+ fireStateChanged();
+ }
}
- public final Insets getInsets()
+ public final Insets getInsets()
{
- return new Insets(0,0,0,0);
+ return new Insets(0, 0, 0, 0);
}
public final Insets getInsets(Insets insets)
@@ -390,6 +453,14 @@ public class JViewport extends JComponent
return insets;
}
+
+ /**
+ * Overridden to return <code>false</code>, so the JViewport's paint method
+ * gets called instead of directly calling the children. This is necessary
+ * in order to get a useful clipping and translation on the children.
+ *
+ * @return <code>false</code>
+ */
public boolean isOptimizedDrawingEnabled()
{
return false;
@@ -397,7 +468,36 @@ public class JViewport extends JComponent
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)
@@ -415,13 +515,6 @@ public class JViewport extends JComponent
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.
*
@@ -467,6 +560,90 @@ public class JViewport extends JComponent
}
/**
+ * Scrolls the view so that contentRect becomes visible.
+ *
+ * @param contentRect the rectangle to make visible within the view
+ */
+ public void scrollRectToVisible(Rectangle contentRect)
+ {
+ Component view = getView();
+ if (view == null)
+ return;
+
+ Point pos = getViewPosition();
+ Rectangle viewBounds = getView().getBounds();
+ Rectangle portBounds = getBounds();
+
+ if (isShowing())
+ getView().validate();
+
+ // If the bottom boundary of contentRect is below the port
+ // boundaries, scroll up as necessary.
+ if (contentRect.y + contentRect.height + viewBounds.y > portBounds.height)
+ pos.y = contentRect.y + contentRect.height - portBounds.height;
+ // If contentRect.y is above the port boundaries, scroll down to
+ // contentRect.y.
+ if (contentRect.y + viewBounds.y < 0)
+ pos.y = contentRect.y;
+ // If the right boundary of contentRect is right from the port
+ // boundaries, scroll left as necessary.
+ if (contentRect.x + contentRect.width + viewBounds.x > portBounds.width)
+ pos.x = contentRect.x + contentRect.width - portBounds.width;
+ // If contentRect.x is left from the port boundaries, scroll right to
+ // contentRect.x.
+ if (contentRect.x + viewBounds.x < 0)
+ pos.x = contentRect.x;
+ setViewPosition(pos);
+ }
+
+ /**
+ * Returns the accessible context for this <code>JViewport</code>. This
+ * will be an instance of {@link AccessibleJViewport}.
+ *
+ * @return the accessible context for this <code>JViewport</code>
+ */
+ public AccessibleContext getAccessibleContext()
+ {
+ if (accessibleContext == null)
+ accessibleContext = new AccessibleJViewport();
+ return accessibleContext;
+ }
+
+ /**
+ * Forward repaint to parent to make sure only one paint is performed by the
+ * RepaintManager.
+ *
+ * @param tm number of milliseconds to defer the repaint request
+ * @param x the X coordinate of the upper left corner of the dirty area
+ * @param y the Y coordinate of the upper left corner of the dirty area
+ * @param w the width of the dirty area
+ * @param h the height of the dirty area
+ */
+ public void repaint(long tm, int x, int y, int w, int h)
+ {
+ Component parent = getParent();
+ if (parent != null)
+ {
+ parent.repaint(tm, x + getX(), y + getY(), w, h);
+ }
+ }
+
+ protected void addImpl(Component comp, Object constraints, int index)
+ {
+ if (getComponentCount() > 0)
+ remove(getComponents()[0]);
+
+ super.addImpl(comp, constraints, index);
+ }
+
+ protected void fireStateChanged()
+ {
+ ChangeListener[] listeners = getChangeListeners();
+ for (int i = 0; i < listeners.length; ++i)
+ listeners[i].stateChanged(changeEvent);
+ }
+
+ /**
* Creates a {@link ViewListener} that is supposed to listen for
* size changes on the view component.
*
@@ -489,43 +666,217 @@ public class JViewport extends JComponent
}
/**
- * Scrolls the view so that contentRect becomes visible.
+ * Computes the parameters for the blitting scroll method. <code>dx</code>
+ * and <code>dy</code> specifiy the X and Y offset by which the viewport
+ * is scrolled. All other arguments are output parameters and are filled by
+ * this method.
*
- * @param contentRect the rectangle to make visible within the view
+ * <code>blitFrom</code> holds the position of the blit rectangle in the
+ * viewport rectangle before scrolling, <code>blitTo</code> where the blitArea
+ * is copied to.
+ *
+ * <code>blitSize</code> holds the size of the blit area and
+ * <code>blitPaint</code> is the area of the view that needs to be painted.
+ *
+ * This method returns <code>true</code> if blitting is possible and
+ * <code>false</code> if the viewport has to be repainted completetly without
+ * blitting.
+ *
+ * @param dx the horizontal delta
+ * @param dy the vertical delta
+ * @param blitFrom the position from where to blit; set by this method
+ * @param blitTo the position where to blit area is copied to; set by this
+ * method
+ * @param blitSize the size of the blitted area; set by this method
+ * @param blitPaint the area that needs repainting; set by this method
+ *
+ * @return <code>true</code> if blitting is possible,
+ * <code>false</code> otherwise
*/
- public void scrollRectToVisible(Rectangle contentRect)
+ 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
+ * {@link #SIMPLE_SCROLL_MODE}.
+ *
+ * This simply paints the view directly on the surface of the viewport.
+ *
+ * @param g the graphics context to use
+ */
+ void paintSimple(Graphics g)
{
Point pos = getViewPosition();
- Rectangle viewBounds = getView().getBounds();
- Rectangle portBounds = getBounds();
-
- // FIXME: should validate the view if it is not valid, however
- // this may cause excessive validation when the containment
- // hierarchy is being created.
-
- // if contentRect is larger than the portBounds, center the view
- if (contentRect.height > portBounds.height ||
- contentRect.width > portBounds.width)
+ Component view = getView();
+ boolean translated = false;
+ try
+ {
+ g.translate(-pos.x, -pos.y);
+ translated = true;
+ view.paint(g);
+ }
+ finally
{
- setViewPosition(new Point(contentRect.x, contentRect.y));
- return;
+ if (translated)
+ g.translate (pos.x, pos.y);
}
-
- // Y-DIRECTION
- if (contentRect.y < -viewBounds.y)
- setViewPosition(new Point(pos.x, contentRect.y));
- else if (contentRect.y + contentRect.height >
- -viewBounds.y + portBounds.height)
- setViewPosition (new Point(pos.x, contentRect.y -
- (portBounds.height - contentRect.height)));
-
- // X-DIRECTION
- pos = getViewPosition();
- if (contentRect.x < -viewBounds.x)
- setViewPosition(new Point(contentRect.x, pos.y));
- else if (contentRect.x + contentRect.width >
- -viewBounds.x + portBounds.width)
- setViewPosition (new Point(contentRect.x -
- (portBounds.width - contentRect.width), pos.y));
+ }
+
+ /**
+ * Paints the viewport in case we have a scroll mode of
+ * {@link #BACKINGSTORE_SCROLL_MODE}.
+ *
+ * This method uses a backing store image to paint the view to, which is then
+ * subsequently painted on the screen. This should make scrolling more
+ * smooth.
+ *
+ * @param g the graphics context to use
+ */
+ void paintBackingStore(Graphics g)
+ {
+ // If we have no backing store image yet or the size of the component has
+ // changed, we need to rebuild the backing store.
+ if (backingStoreImage == null || sizeChanged)
+ {
+ backingStoreImage = createImage(getWidth(), getHeight());
+ sizeChanged = false;
+ Graphics g2 = backingStoreImage.getGraphics();
+ paintSimple(g2);
+ g2.dispose();
+ }
+ // Otherwise we can perform the blitting on the backing store image:
+ // First we move the part that remains visible after scrolling, then
+ // we only need to paint the bit that becomes newly visible.
+ else
+ {
+ Graphics g2 = backingStoreImage.getGraphics();
+ Point viewPosition = getViewPosition();
+ int dx = viewPosition.x - lastPaintPosition.x;
+ int dy = viewPosition.y - lastPaintPosition.y;
+ boolean canBlit = computeBlit(dx, dy, cachedBlitFrom, cachedBlitTo,
+ cachedBlitSize, cachedBlitPaint);
+ if (canBlit)
+ {
+ // Copy the part that remains visible during scrolling.
+ g2.copyArea(cachedBlitFrom.x, cachedBlitFrom.y,
+ cachedBlitSize.width, cachedBlitSize.height,
+ cachedBlitTo.x - cachedBlitFrom.x,
+ cachedBlitTo.y - cachedBlitFrom.y);
+ // Now paint the part that becomes newly visible.
+ g2.setClip(cachedBlitPaint.x, cachedBlitPaint.y,
+ cachedBlitPaint.width, cachedBlitPaint.height);
+ paintSimple(g2);
+ }
+ // If blitting is not possible for some reason, fall back to repainting
+ // everything.
+ else
+ {
+ paintSimple(g2);
+ }
+ g2.dispose();
+ }
+ // Actually draw the backingstore image to the graphics context.
+ g.drawImage(backingStoreImage, 0, 0, this);
+ // Update the lastPaintPosition so that we know what is already drawn when
+ // we paint the next time.
+ lastPaintPosition.setLocation(getViewPosition());
+ }
+
+ /**
+ * Paints the viewport in case we have a scrollmode of
+ * {@link #BLIT_SCROLL_MODE}.
+ *
+ * This paints the viewport using a backingstore and a blitting algorithm.
+ * Only the newly exposed area of the view is painted from the view painting
+ * methods, the remainder is copied from the backing store.
+ *
+ * @param g the graphics context to use
+ */
+ void paintBlit(Graphics g)
+ {
+ // 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);
}
}