diff -r 4ebc2e2fb97c -r 71c04702a3d5 src/java.desktop/share/classes/javax/swing/RepaintManager.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.desktop/share/classes/javax/swing/RepaintManager.java Tue Sep 12 19:03:39 2017 +0200 @@ -0,0 +1,1890 @@ +/* + * Copyright (c) 1997, 2015, Oracle and/or its affiliates. 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.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.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.applet.*; + +import jdk.internal.misc.JavaSecurityAccess; +import jdk.internal.misc.SharedSecrets; +import sun.awt.AWTAccessor; +import sun.awt.AppContext; +import sun.awt.DisplayChangedListener; +import sun.awt.SunToolkit; +import sun.java2d.SunGraphicsEnvironment; +import sun.security.action.GetPropertyAction; + +import com.sun.java.swing.SwingUtilities3; +import java.awt.geom.AffineTransform; +import sun.java2d.SunGraphics2D; +import sun.java2d.pipe.Region; +import sun.swing.SwingAccessor; +import sun.swing.SwingUtilities2; +import sun.swing.SwingUtilities2.RepaintListener; + +/** + * This class manages repaint requests, allowing the number + * of repaints to be minimized, for example by collapsing multiple + * requests into a single repaint for members of a component tree. + *
+ * As of 1.6
+ * Note: This method exists for backward binary compatibility with earlier
+ * versions of the Swing library. It simply returns the result returned by
+ * {@link #currentManager(Component)}.
+ *
+ * @param c a JComponent -- unused
+ * @return the RepaintManager object
+ */
+ public static RepaintManager currentManager(JComponent c) {
+ return currentManager((Component)c);
+ }
+
+
+ /**
+ * Set the RepaintManager that should be used for the calling
+ * thread. aRepaintManager will become the current RepaintManager
+ * for the calling thread's thread group.
+ * @param aRepaintManager the RepaintManager object to use
+ */
+ public static void setCurrentManager(RepaintManager aRepaintManager) {
+ if (aRepaintManager != null) {
+ SwingUtilities.appContextPut(repaintManagerKey, aRepaintManager);
+ } else {
+ SwingUtilities.appContextRemove(repaintManagerKey);
+ }
+ }
+
+ /**
+ * Create a new RepaintManager instance. You rarely call this constructor.
+ * directly. To get the default RepaintManager, use
+ * RepaintManager.currentManager(JComponent) (normally "this").
+ */
+ public RepaintManager() {
+ // Because we can't know what a subclass is doing with the
+ // volatile image we immediately punt in subclasses. If this
+ // poses a problem we'll need a more sophisticated detection algorithm,
+ // or API.
+ this(BUFFER_STRATEGY_SPECIFIED_OFF);
+ }
+
+ private RepaintManager(short bufferStrategyType) {
+ // If native doublebuffering is being used, do NOT use
+ // Swing doublebuffering.
+ doubleBufferingEnabled = !nativeDoubleBuffering;
+ synchronized(this) {
+ dirtyComponents = new IdentityHashMap
+ * WARNING: This method is invoked from the native toolkit thread, be
+ * very careful as to what methods this invokes!
+ */
+ boolean show(Container c, int x, int y, int w, int h) {
+ return getPaintManager().show(c, x, y, w, h);
+ }
+
+ /**
+ * Invoked when the doubleBuffered or useTrueDoubleBuffering
+ * properties of a JRootPane change. This may come in on any thread.
+ */
+ void doubleBufferingChanged(JRootPane rootPane) {
+ getPaintManager().doubleBufferingChanged(rootPane);
+ }
+
+ /**
+ * Sets the RepaintManager
handles repaint requests
+ * for Swing's top level components (JApplet
,
+ * JWindow
, JFrame
and JDialog
).
+ * Any calls to repaint
on one of these will call into the
+ * appropriate addDirtyRegion
method.
+ *
+ * @author Arnaud Weber
+ * @since 1.2
+ */
+public class RepaintManager
+{
+ /**
+ * Whether or not the RepaintManager should handle paint requests
+ * for top levels.
+ */
+ static final boolean HANDLE_TOP_LEVEL_PAINT;
+
+ private static final short BUFFER_STRATEGY_NOT_SPECIFIED = 0;
+ private static final short BUFFER_STRATEGY_SPECIFIED_ON = 1;
+ private static final short BUFFER_STRATEGY_SPECIFIED_OFF = 2;
+
+ private static final short BUFFER_STRATEGY_TYPE;
+
+ /**
+ * Maps from GraphicsConfiguration to VolatileImage.
+ */
+ private MapbeginPaint
that have been invoked.
+ */
+ private int paintDepth = 0;
+
+ /**
+ * Type of buffer strategy to use. Will be one of the BUFFER_STRATEGY_
+ * constants.
+ */
+ private short bufferStrategyType;
+
+ //
+ // BufferStrategyPaintManager has the unique characteristic that it
+ // must deal with the buffer being lost while painting to it. For
+ // example, if we paint a component and show it and the buffer has
+ // become lost we must repaint the whole window. To deal with that
+ // the PaintManager calls into repaintRoot, and if we're still in
+ // the process of painting the repaintRoot field is set to the JRootPane
+ // and after the current JComponent.paintImmediately call finishes
+ // paintImmediately will be invoked on the repaintRoot. In this
+ // way we don't try to show garbage to the screen.
+ //
+ /**
+ * True if we're in the process of painting the dirty regions. This is
+ * set to true in paintDirtyRegions
.
+ */
+ private boolean painting;
+ /**
+ * If the PaintManager calls into repaintRoot during painting this field
+ * will be set to the root.
+ */
+ private JComponent repaintRoot;
+
+ /**
+ * The Thread that has initiated painting. If null it
+ * indicates painting is not currently in progress.
+ */
+ private Thread paintThread;
+
+ /**
+ * Runnable used to process all repaint/revalidate requests.
+ */
+ private final ProcessingRunnable processingRunnable;
+
+ private static final JavaSecurityAccess javaSecurityAccess =
+ SharedSecrets.getJavaSecurityAccess();
+
+ /**
+ * Listener installed to detect display changes. When display changes,
+ * schedules a callback to notify all RepaintManagers of the display
+ * changes.
+ */
+ private static final DisplayChangedListener displayChangedHandler =
+ new DisplayChangedHandler();
+
+ static {
+ SwingAccessor.setRepaintManagerAccessor(new SwingAccessor.RepaintManagerAccessor() {
+ @Override
+ public void addRepaintListener(RepaintManager rm, RepaintListener l) {
+ rm.addRepaintListener(l);
+ }
+ @Override
+ public void removeRepaintListener(RepaintManager rm, RepaintListener l) {
+ rm.removeRepaintListener(l);
+ }
+ });
+
+ volatileImageBufferEnabled = "true".equals(AccessController.
+ doPrivileged(new GetPropertyAction(
+ "swing.volatileImageBufferEnabled", "true")));
+ boolean headless = GraphicsEnvironment.isHeadless();
+ if (volatileImageBufferEnabled && headless) {
+ volatileImageBufferEnabled = false;
+ }
+ nativeDoubleBuffering = "true".equals(AccessController.doPrivileged(
+ new GetPropertyAction("awt.nativeDoubleBuffering")));
+ String bs = AccessController.doPrivileged(
+ new GetPropertyAction("swing.bufferPerWindow"));
+ if (headless) {
+ BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_SPECIFIED_OFF;
+ }
+ else if (bs == null) {
+ BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_NOT_SPECIFIED;
+ }
+ else if ("true".equals(bs)) {
+ BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_SPECIFIED_ON;
+ }
+ else {
+ BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_SPECIFIED_OFF;
+ }
+ HANDLE_TOP_LEVEL_PAINT = "true".equals(AccessController.doPrivileged(
+ new GetPropertyAction("swing.handleTopLevelPaint", "true")));
+ GraphicsEnvironment ge = GraphicsEnvironment.
+ getLocalGraphicsEnvironment();
+ if (ge instanceof SunGraphicsEnvironment) {
+ ((SunGraphicsEnvironment) ge).addDisplayChangedListener(
+ displayChangedHandler);
+ }
+ Toolkit tk = Toolkit.getDefaultToolkit();
+ if ((tk instanceof SunToolkit)
+ && ((SunToolkit) tk).isSwingBackbufferTranslucencySupported()) {
+ volatileBufferType = Transparency.TRANSLUCENT;
+ } else {
+ volatileBufferType = Transparency.OPAQUE;
+ }
+ }
+
+ /**
+ * Return the RepaintManager for the calling thread given a Component.
+ *
+ * @param c a Component -- unused in the default implementation, but could
+ * be used by an overridden version to return a different RepaintManager
+ * depending on the Component
+ * @return the RepaintManager object
+ */
+ public static RepaintManager currentManager(Component c) {
+ // Note: DisplayChangedRunnable passes in null as the component, so if
+ // component is ever used to determine the current
+ // RepaintManager, DisplayChangedRunnable will need to be modified
+ // accordingly.
+ return currentManager(AppContext.getAppContext());
+ }
+
+ /**
+ * Returns the RepaintManager for the specified AppContext. If
+ * a RepaintManager has not been created for the specified
+ * AppContext this will return null.
+ */
+ static RepaintManager currentManager(AppContext appContext) {
+ RepaintManager rm = (RepaintManager)appContext.get(repaintManagerKey);
+ if (rm == null) {
+ rm = new RepaintManager(BUFFER_STRATEGY_TYPE);
+ appContext.put(repaintManagerKey, rm);
+ }
+ return rm;
+ }
+
+ /**
+ * Return the RepaintManager for the calling thread given a JComponent.
+ * window
to the list of Component
s that
+ * need to be repainted.
+ *
+ * @param window Window to repaint, null results in nothing happening.
+ * @param x X coordinate of the region to repaint
+ * @param y Y coordinate of the region to repaint
+ * @param w Width of the region to repaint
+ * @param h Height of the region to repaint
+ * @see JFrame#repaint
+ * @see JWindow#repaint
+ * @see JDialog#repaint
+ * @since 1.6
+ */
+ public void addDirtyRegion(Window window, int x, int y, int w, int h) {
+ addDirtyRegion0(window, x, y, w, h);
+ }
+
+ /**
+ * Adds applet
to the list of Component
s that
+ * need to be repainted.
+ *
+ * @param applet Applet to repaint, null results in nothing happening.
+ * @param x X coordinate of the region to repaint
+ * @param y Y coordinate of the region to repaint
+ * @param w Width of the region to repaint
+ * @param h Height of the region to repaint
+ * @see JApplet#repaint
+ * @since 1.6
+ *
+ * @deprecated The Applet API is deprecated. See the
+ * java.applet package
+ * documentation for further information.
+ */
+ @Deprecated(since = "9")
+ public void addDirtyRegion(Applet applet, int x, int y, int w, int h) {
+ addDirtyRegion0(applet, x, y, w, h);
+ }
+
+ @SuppressWarnings("deprecation")
+ void scheduleHeavyWeightPaints() {
+ Mapc
is not yet marked dirty.
+ */
+ private synchronized boolean extendDirtyRegion(
+ Component c, int x, int y, int w, int h) {
+ Rectangle r = dirtyComponents.get(c);
+ if (r != null) {
+ // A non-null r implies c is already marked as dirty,
+ // and that the parent is valid. Therefore we can
+ // just union the rect and bail.
+ SwingUtilities.computeUnion(x, y, w, h, r);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Return the current dirty region for a component.
+ * Return an empty rectangle if the component is not
+ * dirty.
+ *
+ * @param aComponent a component
+ * @return the region
+ */
+ public Rectangle getDirtyRegion(JComponent aComponent) {
+ RepaintManager delegate = getDelegate(aComponent);
+ if (delegate != null) {
+ return delegate.getDirtyRegion(aComponent);
+ }
+ Rectangle r;
+ synchronized(this) {
+ r = dirtyComponents.get(aComponent);
+ }
+ if(r == null)
+ return new Rectangle(0,0,0,0);
+ else
+ return new Rectangle(r);
+ }
+
+ /**
+ * Mark a component completely dirty. aComponent will be
+ * completely painted during the next paintDirtyRegions() call.
+ *
+ * @param aComponent a component
+ */
+ public void markCompletelyDirty(JComponent aComponent) {
+ RepaintManager delegate = getDelegate(aComponent);
+ if (delegate != null) {
+ delegate.markCompletelyDirty(aComponent);
+ return;
+ }
+ addDirtyRegion(aComponent,0,0,Integer.MAX_VALUE,Integer.MAX_VALUE);
+ }
+
+ /**
+ * Mark a component completely clean. aComponent will not
+ * get painted during the next paintDirtyRegions() call.
+ *
+ * @param aComponent a component
+ */
+ public void markCompletelyClean(JComponent aComponent) {
+ RepaintManager delegate = getDelegate(aComponent);
+ if (delegate != null) {
+ delegate.markCompletelyClean(aComponent);
+ return;
+ }
+ synchronized(this) {
+ dirtyComponents.remove(aComponent);
+ }
+ }
+
+ /**
+ * Convenience method that returns true if aComponent will be completely
+ * painted during the next paintDirtyRegions(). If computing dirty regions is
+ * expensive for your component, use this method and avoid computing dirty region
+ * if it return true.
+ *
+ * @param aComponent a component
+ * @return {@code true} if aComponent will be completely
+ * painted during the next paintDirtyRegions().
+ */
+ public boolean isCompletelyDirty(JComponent aComponent) {
+ RepaintManager delegate = getDelegate(aComponent);
+ if (delegate != null) {
+ return delegate.isCompletelyDirty(aComponent);
+ }
+ Rectangle r;
+
+ r = getDirtyRegion(aComponent);
+ if(r.width == Integer.MAX_VALUE &&
+ r.height == Integer.MAX_VALUE)
+ return true;
+ else
+ return false;
+ }
+
+
+ /**
+ * Validate all of the components that have been marked invalid.
+ * @see #addInvalidComponent
+ */
+ public void validateInvalidComponents() {
+ final java.util.Listc
.
+ * By default there is a double buffer per RepaintManager.
+ * The buffer might be smaller than (proposedWidth,proposedHeight)
+ * This happens when the maximum double buffer size as been set for the receiving
+ * repaint manager.
+ *
+ * @param c the component
+ * @param proposedWidth the width of the buffer
+ * @param proposedHeight the height of the buffer
+ *
+ * @return the image
+ */
+ public Image getOffscreenBuffer(Component c,int proposedWidth,int proposedHeight) {
+ RepaintManager delegate = getDelegate(c);
+ if (delegate != null) {
+ return delegate.getOffscreenBuffer(c, proposedWidth, proposedHeight);
+ }
+ return _getOffscreenBuffer(c, proposedWidth, proposedHeight);
+ }
+
+ /**
+ * Return a volatile offscreen buffer that should be used as a
+ * double buffer with the specified component c
.
+ * The image returned will be an instance of VolatileImage, or null
+ * if a VolatileImage object could not be instantiated.
+ * This buffer might be smaller than (proposedWidth,proposedHeight)
.
+ * This happens when the maximum double buffer size has been set for this
+ * repaint manager.
+ *
+ * @param c the component
+ * @param proposedWidth the width of the buffer
+ * @param proposedHeight the height of the buffer
+ *
+ * @return the volatile image
+ * @see java.awt.image.VolatileImage
+ * @since 1.4
+ */
+ public Image getVolatileOffscreenBuffer(Component c,
+ int proposedWidth,int proposedHeight) {
+ RepaintManager delegate = getDelegate(c);
+ if (delegate != null) {
+ return delegate.getVolatileOffscreenBuffer(c, proposedWidth,
+ proposedHeight);
+ }
+
+ // If the window is non-opaque, it's double-buffered at peer's level
+ Window w = (c instanceof Window) ? (Window)c : SwingUtilities.getWindowAncestor(c);
+ if (!w.isOpaque()) {
+ Toolkit tk = Toolkit.getDefaultToolkit();
+ if ((tk instanceof SunToolkit) && (((SunToolkit)tk).needUpdateWindow())) {
+ return null;
+ }
+ }
+
+ GraphicsConfiguration config = c.getGraphicsConfiguration();
+ if (config == null) {
+ config = GraphicsEnvironment.getLocalGraphicsEnvironment().
+ getDefaultScreenDevice().getDefaultConfiguration();
+ }
+ Dimension maxSize = getDoubleBufferMaximumSize();
+ int width = proposedWidth < 1 ? 1 :
+ (proposedWidth > maxSize.width? maxSize.width : proposedWidth);
+ int height = proposedHeight < 1 ? 1 :
+ (proposedHeight > maxSize.height? maxSize.height : proposedHeight);
+ VolatileImage image = volatileMap.get(config);
+ if (image == null || image.getWidth() < width ||
+ image.getHeight() < height) {
+ if (image != null) {
+ image.flush();
+ }
+ image = config.createCompatibleVolatileImage(width, height,
+ volatileBufferType);
+ volatileMap.put(config, image);
+ }
+ return image;
+ }
+
+ private Image _getOffscreenBuffer(Component c, int proposedWidth, int proposedHeight) {
+ Dimension maxSize = getDoubleBufferMaximumSize();
+ DoubleBufferInfo doubleBuffer;
+ int width, height;
+
+ // If the window is non-opaque, it's double-buffered at peer's level
+ Window w = (c instanceof Window) ? (Window)c : SwingUtilities.getWindowAncestor(c);
+ if (!w.isOpaque()) {
+ Toolkit tk = Toolkit.getDefaultToolkit();
+ if ((tk instanceof SunToolkit) && (((SunToolkit)tk).needUpdateWindow())) {
+ return null;
+ }
+ }
+
+ if (standardDoubleBuffer == null) {
+ standardDoubleBuffer = new DoubleBufferInfo();
+ }
+ doubleBuffer = standardDoubleBuffer;
+
+ width = proposedWidth < 1? 1 :
+ (proposedWidth > maxSize.width? maxSize.width : proposedWidth);
+ height = proposedHeight < 1? 1 :
+ (proposedHeight > maxSize.height? maxSize.height : proposedHeight);
+
+ if (doubleBuffer.needsReset || (doubleBuffer.image != null &&
+ (doubleBuffer.size.width < width ||
+ doubleBuffer.size.height < height))) {
+ doubleBuffer.needsReset = false;
+ if (doubleBuffer.image != null) {
+ doubleBuffer.image.flush();
+ doubleBuffer.image = null;
+ }
+ width = Math.max(doubleBuffer.size.width, width);
+ height = Math.max(doubleBuffer.size.height, height);
+ }
+
+ Image result = doubleBuffer.image;
+
+ if (doubleBuffer.image == null) {
+ result = c.createImage(width , height);
+ doubleBuffer.size = new Dimension(width, height);
+ if (c instanceof JComponent) {
+ ((JComponent)c).setCreatedDoubleBuffer(true);
+ doubleBuffer.image = result;
+ }
+ // JComponent will inform us when it is no longer valid
+ // (via removeNotify) we have no such hook to other components,
+ // therefore we don't keep a ref to the Component
+ // (indirectly through the Image) by stashing the image.
+ }
+ return result;
+ }
+
+
+ /**
+ * Set the maximum double buffer size.
+ *
+ * @param d the dimension
+ */
+ public void setDoubleBufferMaximumSize(Dimension d) {
+ doubleBufferMaxSize = d;
+ if (doubleBufferMaxSize == null) {
+ clearImages();
+ } else {
+ clearImages(d.width, d.height);
+ }
+ }
+
+ private void clearImages() {
+ clearImages(0, 0);
+ }
+
+ private void clearImages(int width, int height) {
+ if (standardDoubleBuffer != null && standardDoubleBuffer.image != null) {
+ if (standardDoubleBuffer.image.getWidth(null) > width ||
+ standardDoubleBuffer.image.getHeight(null) > height) {
+ standardDoubleBuffer.image.flush();
+ standardDoubleBuffer.image = null;
+ }
+ }
+ // Clear out the VolatileImages
+ Iteratorfalse
+ * to avoid unnecessary buffering in Swing.
+ * On platforms where native double buffering is not supported,
+ * the default value will be true
.
+ *
+ * @return true if this object is double buffered
+ */
+ public boolean isDoubleBufferingEnabled() {
+ return doubleBufferingEnabled;
+ }
+
+ /**
+ * This resets the double buffer. Actually, it marks the double buffer
+ * as invalid, the double buffer will then be recreated on the next
+ * invocation of getOffscreenBuffer.
+ */
+ void resetDoubleBuffer() {
+ if (standardDoubleBuffer != null) {
+ standardDoubleBuffer.needsReset = true;
+ }
+ }
+
+ /**
+ * This resets the volatile double buffer.
+ */
+ void resetVolatileDoubleBuffer(GraphicsConfiguration gc) {
+ Image image = volatileMap.remove(gc);
+ if (image != null) {
+ image.flush();
+ }
+ }
+
+ /**
+ * Returns true if we should use the Image
returned
+ * from getVolatileOffscreenBuffer
to do double buffering.
+ */
+ boolean useVolatileDoubleBuffer() {
+ return volatileImageBufferEnabled;
+ }
+
+ /**
+ * Returns true if the current thread is the thread painting. This
+ * will return false if no threads are painting.
+ */
+ private synchronized boolean isPaintingThread() {
+ return (Thread.currentThread() == paintThread);
+ }
+ //
+ // Paint methods. You very, VERY rarely need to invoke these.
+ // They are invoked directly from JComponent's painting code and
+ // when painting happens outside the normal flow: DefaultDesktopManager
+ // and JViewport. If you end up needing these methods in other places be
+ // careful that you don't get stuck in a paint loop.
+ //
+
+ /**
+ * Paints a region of a component
+ *
+ * @param paintingComponent Component to paint
+ * @param bufferComponent Component to obtain buffer for
+ * @param g Graphics to paint to
+ * @param x X-coordinate
+ * @param y Y-coordinate
+ * @param w Width
+ * @param h Height
+ */
+ void paint(JComponent paintingComponent,
+ JComponent bufferComponent, Graphics g,
+ int x, int y, int w, int h) {
+ PaintManager paintManager = getPaintManager();
+ if (!isPaintingThread()) {
+ // We're painting to two threads at once. PaintManager deals
+ // with this a bit better than BufferStrategyPaintManager, use
+ // it to avoid possible exceptions/corruption.
+ if (paintManager.getClass() != PaintManager.class) {
+ paintManager = new PaintManager();
+ paintManager.repaintManager = this;
+ }
+ }
+ if (!paintManager.paint(paintingComponent, bufferComponent, g,
+ x, y, w, h)) {
+ g.setClip(x, y, w, h);
+ paintingComponent.paintToOffscreen(g, x, y, w, h, x + w, y + h);
+ }
+ }
+
+ /**
+ * Does a copy area on the specified region.
+ *
+ * @param clip Whether or not the copyArea needs to be clipped to the
+ * Component's bounds.
+ */
+ void copyArea(JComponent c, Graphics g, int x, int y, int w, int h,
+ int deltaX, int deltaY, boolean clip) {
+ getPaintManager().copyArea(c, g, x, y, w, h, deltaX, deltaY, clip);
+ }
+
+ private java.util.ListendPaint
.
+ * WARNING: Callers of this method need to wrap the call
+ * in a try/finally
, otherwise if an exception is thrown
+ * during the course of painting the RepaintManager may
+ * be left in a state in which the screen is not updated, eg:
+ *
+ * repaintManager.beginPaint();
+ * try {
+ * repaintManager.paint(...);
+ * } finally {
+ * repaintManager.endPaint();
+ * }
+ *
+ */
+ void beginPaint() {
+ boolean multiThreadedPaint = false;
+ int paintDepth;
+ Thread currentThread = Thread.currentThread();
+ synchronized(this) {
+ paintDepth = this.paintDepth;
+ if (paintThread == null || currentThread == paintThread) {
+ paintThread = currentThread;
+ this.paintDepth++;
+ } else {
+ multiThreadedPaint = true;
+ }
+ }
+ if (!multiThreadedPaint && paintDepth == 0) {
+ getPaintManager().beginPaint();
+ }
+ }
+
+ /**
+ * Invoked after beginPaint
has been invoked.
+ */
+ void endPaint() {
+ if (isPaintingThread()) {
+ PaintManager paintManager = null;
+ synchronized(this) {
+ if (--paintDepth == 0) {
+ paintManager = getPaintManager();
+ }
+ }
+ if (paintManager != null) {
+ paintManager.endPaint();
+ synchronized(this) {
+ paintThread = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * If possible this will show a previously rendered portion of
+ * a Component. If successful, this will return true, otherwise false.
+ * PaintManager
that is used to handle all
+ * double buffered painting.
+ *
+ * @param paintManager The PaintManager to use. Passing in null indicates
+ * the fallback PaintManager should be used.
+ */
+ void setPaintManager(PaintManager paintManager) {
+ if (paintManager == null) {
+ paintManager = new PaintManager();
+ }
+ PaintManager oldPaintManager;
+ synchronized(this) {
+ oldPaintManager = this.paintManager;
+ this.paintManager = paintManager;
+ paintManager.repaintManager = this;
+ }
+ if (oldPaintManager != null) {
+ oldPaintManager.dispose();
+ }
+ }
+
+ private synchronized PaintManager getPaintManager() {
+ if (paintManager == null) {
+ PaintManager paintManager = null;
+ if (doubleBufferingEnabled && !nativeDoubleBuffering) {
+ switch (bufferStrategyType) {
+ case BUFFER_STRATEGY_NOT_SPECIFIED:
+ Toolkit tk = Toolkit.getDefaultToolkit();
+ if (tk instanceof SunToolkit) {
+ SunToolkit stk = (SunToolkit) tk;
+ if (stk.useBufferPerWindow()) {
+ paintManager = new BufferStrategyPaintManager();
+ }
+ }
+ break;
+ case BUFFER_STRATEGY_SPECIFIED_ON:
+ paintManager = new BufferStrategyPaintManager();
+ break;
+ default:
+ break;
+ }
+ }
+ // null case handled in setPaintManager
+ setPaintManager(paintManager);
+ }
+ return paintManager;
+ }
+
+ private void scheduleProcessingRunnable(AppContext context) {
+ if (processingRunnable.markPending()) {
+ Toolkit tk = Toolkit.getDefaultToolkit();
+ if (tk instanceof SunToolkit) {
+ SunToolkit.getSystemEventQueueImplPP(context).
+ postEvent(new InvocationEvent(Toolkit.getDefaultToolkit(),
+ processingRunnable));
+ } else {
+ Toolkit.getDefaultToolkit().getSystemEventQueue().
+ postEvent(new InvocationEvent(Toolkit.getDefaultToolkit(),
+ processingRunnable));
+ }
+ }
+ }
+
+
+ /**
+ * PaintManager is used to handle all double buffered painting for
+ * Swing. Subclasses should call back into the JComponent method
+ * paintToOffscreen
to handle the actual painting.
+ */
+ static class PaintManager {
+ /**
+ * RepaintManager the PaintManager has been installed on.
+ */
+ protected RepaintManager repaintManager;
+ boolean isRepaintingRoot;
+
+ /**
+ * Paints a region of a component
+ *
+ * @param paintingComponent Component to paint
+ * @param bufferComponent Component to obtain buffer for
+ * @param g Graphics to paint to
+ * @param x X-coordinate
+ * @param y Y-coordinate
+ * @param w Width
+ * @param h Height
+ * @return true if painting was successful.
+ */
+ public boolean paint(JComponent paintingComponent,
+ JComponent bufferComponent, Graphics g,
+ int x, int y, int w, int h) {
+ // First attempt to use VolatileImage buffer for performance.
+ // If this fails (which should rarely occur), fallback to a
+ // standard Image buffer.
+ boolean paintCompleted = false;
+ Image offscreen;
+ int sw = w + 1;
+ int sh = h + 1;
+
+ if (repaintManager.useVolatileDoubleBuffer() &&
+ (offscreen = getValidImage(repaintManager.
+ getVolatileOffscreenBuffer(bufferComponent, sw, sh))) != null) {
+ VolatileImage vImage = (java.awt.image.VolatileImage)offscreen;
+ GraphicsConfiguration gc = bufferComponent.
+ getGraphicsConfiguration();
+ for (int i = 0; !paintCompleted &&
+ i < RepaintManager.VOLATILE_LOOP_MAX; i++) {
+ if (vImage.validate(gc) ==
+ VolatileImage.IMAGE_INCOMPATIBLE) {
+ repaintManager.resetVolatileDoubleBuffer(gc);
+ offscreen = repaintManager.getVolatileOffscreenBuffer(
+ bufferComponent, sw, sh);
+ vImage = (java.awt.image.VolatileImage)offscreen;
+ }
+ paintDoubleBuffered(paintingComponent, vImage, g, x, y,
+ w, h);
+ paintCompleted = !vImage.contentsLost();
+ }
+ }
+ // VolatileImage painting loop failed, fallback to regular
+ // offscreen buffer
+ if (!paintCompleted && (offscreen = getValidImage(
+ repaintManager.getOffscreenBuffer(
+ bufferComponent, w, h))) != null) {
+ paintDoubleBuffered(paintingComponent, offscreen, g, x, y, w,
+ h);
+ paintCompleted = true;
+ }
+ return paintCompleted;
+ }
+
+ /**
+ * Does a copy area on the specified region.
+ */
+ public void copyArea(JComponent c, Graphics g, int x, int y, int w,
+ int h, int deltaX, int deltaY, boolean clip) {
+ g.copyArea(x, y, w, h, deltaX, deltaY);
+ }
+
+ /**
+ * Invoked prior to any calls to paint or copyArea.
+ */
+ public void beginPaint() {
+ }
+
+ /**
+ * Invoked to indicate painting has been completed.
+ */
+ public void endPaint() {
+ }
+
+ /**
+ * Shows a region of a previously rendered component. This
+ * will return true if successful, false otherwise. The default
+ * implementation returns false.
+ */
+ public boolean show(Container c, int x, int y, int w, int h) {
+ return false;
+ }
+
+ /**
+ * Invoked when the doubleBuffered or useTrueDoubleBuffering
+ * properties of a JRootPane change. This may come in on any thread.
+ */
+ public void doubleBufferingChanged(JRootPane rootPane) {
+ }
+
+ /**
+ * Paints a portion of a component to an offscreen buffer.
+ */
+ protected void paintDoubleBuffered(JComponent c, Image image,
+ Graphics g, int clipX, int clipY,
+ int clipW, int clipH) {
+ if (image instanceof VolatileImage && isPixelsCopying(c, g)) {
+ paintDoubleBufferedFPScales(c, image, g, clipX, clipY, clipW, clipH);
+ } else {
+ paintDoubleBufferedImpl(c, image, g, clipX, clipY, clipW, clipH);
+ }
+ }
+
+ private void paintDoubleBufferedImpl(JComponent c, Image image,
+ Graphics g, int clipX, int clipY,
+ int clipW, int clipH) {
+ Graphics osg = image.getGraphics();
+ int bw = Math.min(clipW, image.getWidth(null));
+ int bh = Math.min(clipH, image.getHeight(null));
+ int x,y,maxx,maxy;
+
+ try {
+ for(x = clipX, maxx = clipX+clipW; x < maxx ; x += bw ) {
+ for(y=clipY, maxy = clipY + clipH; y < maxy ; y += bh) {
+ osg.translate(-x, -y);
+ osg.setClip(x,y,bw,bh);
+ if (volatileBufferType != Transparency.OPAQUE
+ && osg instanceof Graphics2D) {
+ final Graphics2D g2d = (Graphics2D) osg;
+ final Color oldBg = g2d.getBackground();
+ g2d.setBackground(c.getBackground());
+ g2d.clearRect(x, y, bw, bh);
+ g2d.setBackground(oldBg);
+ }
+ c.paintToOffscreen(osg, x, y, bw, bh, maxx, maxy);
+ g.setClip(x, y, bw, bh);
+ if (volatileBufferType != Transparency.OPAQUE
+ && g instanceof Graphics2D) {
+ final Graphics2D g2d = (Graphics2D) g;
+ final Composite oldComposite = g2d.getComposite();
+ g2d.setComposite(AlphaComposite.Src);
+ g2d.drawImage(image, x, y, c);
+ g2d.setComposite(oldComposite);
+ } else {
+ g.drawImage(image, x, y, c);
+ }
+ osg.translate(x, y);
+ }
+ }
+ } finally {
+ osg.dispose();
+ }
+ }
+
+ private void paintDoubleBufferedFPScales(JComponent c, Image image,
+ Graphics g, int clipX, int clipY,
+ int clipW, int clipH) {
+ Graphics osg = image.getGraphics();
+ Graphics2D g2d = (Graphics2D) g;
+ Graphics2D osg2d = (Graphics2D) osg;
+
+ AffineTransform identity = new AffineTransform();
+ int bw = Math.min(clipW, image.getWidth(null));
+ int bh = Math.min(clipH, image.getHeight(null));
+ int x, y, maxx, maxy;
+
+ AffineTransform tx = g2d.getTransform();
+ double scaleX = tx.getScaleX();
+ double scaleY = tx.getScaleY();
+ double trX = tx.getTranslateX();
+ double trY = tx.getTranslateY();
+
+ boolean translucent = volatileBufferType != Transparency.OPAQUE;
+ Composite oldComposite = g2d.getComposite();
+
+ try {
+ for (x = clipX, maxx = clipX + clipW; x < maxx; x += bw) {
+ for (y = clipY, maxy = clipY + clipH; y < maxy; y += bh) {
+
+ // draw x, y, bw, bh
+ int pixelx1 = Region.clipRound(x * scaleX + trX);
+ int pixely1 = Region.clipRound(y * scaleY + trY);
+ int pixelx2 = Region.clipRound((x + bw) * scaleX + trX);
+ int pixely2 = Region.clipRound((y + bh) * scaleY + trY);
+ int pixelw = pixelx2 - pixelx1;
+ int pixelh = pixely2 - pixely1;
+
+ osg2d.setTransform(identity);
+ if (translucent) {
+ final Color oldBg = g2d.getBackground();
+ g2d.setBackground(c.getBackground());
+ g2d.clearRect(pixelx1, pixely1, pixelw, pixelh);
+ g2d.setBackground(oldBg);
+ }
+
+ osg2d.setClip(0, 0, pixelw, pixelh);
+ osg2d.translate(trX - pixelx1, trY - pixely1);
+ osg2d.scale(scaleX, scaleY);
+ c.paintToOffscreen(osg, x, y, bw, bh, maxx, maxy);
+
+ g2d.setTransform(identity);
+ g2d.setClip(pixelx1, pixely1, pixelw, pixelh);
+ AffineTransform stx = new AffineTransform();
+ stx.translate(pixelx1, pixely1);
+ stx.scale(scaleX, scaleY);
+ g2d.setTransform(stx);
+
+ if (translucent) {
+ g2d.setComposite(AlphaComposite.Src);
+ }
+
+ g2d.drawImage(image, 0, 0, c);
+
+ if (translucent) {
+ g2d.setComposite(oldComposite);
+ }
+ g2d.setTransform(tx);
+ }
+ }
+ } finally {
+ osg.dispose();
+ }
+ }
+
+ /**
+ * If image
is non-null with a positive size it
+ * is returned, otherwise null is returned.
+ */
+ private Image getValidImage(Image image) {
+ if (image != null && image.getWidth(null) > 0 &&
+ image.getHeight(null) > 0) {
+ return image;
+ }
+ return null;
+ }
+
+ /**
+ * Schedules a repaint for the specified component. This differs
+ * from root.repaint
in that if the RepaintManager is
+ * currently processing paint requests it'll process this request
+ * with the current set of requests.
+ */
+ protected void repaintRoot(JComponent root) {
+ assert (repaintManager.repaintRoot == null);
+ if (repaintManager.painting) {
+ repaintManager.repaintRoot = root;
+ }
+ else {
+ root.repaint();
+ }
+ }
+
+ /**
+ * Returns true if the component being painted is the root component
+ * that was previously passed to repaintRoot
.
+ */
+ protected boolean isRepaintingRoot() {
+ return isRepaintingRoot;
+ }
+
+ /**
+ * Cleans up any state. After invoked the PaintManager will no
+ * longer be used anymore.
+ */
+ protected void dispose() {
+ }
+
+ private boolean isPixelsCopying(JComponent c, Graphics g) {
+
+ AffineTransform tx = getTransform(g);
+ GraphicsConfiguration gc = c.getGraphicsConfiguration();
+
+ if (tx == null || gc == null
+ || !SwingUtilities2.isFloatingPointScale(tx)) {
+ return false;
+ }
+
+ AffineTransform gcTx = gc.getDefaultTransform();
+
+ return gcTx.getScaleX() == tx.getScaleX()
+ && gcTx.getScaleY() == tx.getScaleY();
+ }
+
+ private static AffineTransform getTransform(Graphics g) {
+ if (g instanceof SunGraphics2D) {
+ return ((SunGraphics2D) g).transform;
+ } else if (g instanceof Graphics2D) {
+ return ((Graphics2D) g).getTransform();
+ }
+ return null;
+ }
+ }
+
+ private class DoubleBufferInfo {
+ public Image image;
+ public Dimension size;
+ public boolean needsReset = false;
+ }
+
+
+ /**
+ * Listener installed to detect display changes. When display changes,
+ * schedules a callback to notify all RepaintManagers of the display
+ * changes. Only one DisplayChangedHandler is ever installed. The
+ * singleton instance will schedule notification for all AppContexts.
+ */
+ private static final class DisplayChangedHandler implements
+ DisplayChangedListener {
+ // Empty non private constructor was added because access to this
+ // class shouldn't be generated by the compiler using synthetic
+ // accessor method
+ DisplayChangedHandler() {
+ }
+
+ public void displayChanged() {
+ scheduleDisplayChanges();
+ }
+
+ public void paletteChanged() {
+ }
+
+ private static void scheduleDisplayChanges() {
+ // To avoid threading problems, we notify each RepaintManager
+ // on the thread it was created on.
+ for (AppContext context : AppContext.getAppContexts()) {
+ synchronized(context) {
+ if (!context.isDisposed()) {
+ EventQueue eventQueue = (EventQueue)context.get(
+ AppContext.EVENT_QUEUE_KEY);
+ if (eventQueue != null) {
+ eventQueue.postEvent(new InvocationEvent(
+ Toolkit.getDefaultToolkit(),
+ new DisplayChangedRunnable()));
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ private static final class DisplayChangedRunnable implements Runnable {
+ public void run() {
+ RepaintManager.currentManager((JComponent)null).displayChanged();
+ }
+ }
+
+
+ /**
+ * Runnable used to process all repaint/revalidate requests.
+ */
+ private final class ProcessingRunnable implements Runnable {
+ // If true, we're wainting on the EventQueue.
+ private boolean pending;
+
+ /**
+ * Marks this processing runnable as pending. If this was not
+ * already marked as pending, true is returned.
+ */
+ public synchronized boolean markPending() {
+ if (!pending) {
+ pending = true;
+ return true;
+ }
+ return false;
+ }
+
+ public void run() {
+ synchronized (this) {
+ pending = false;
+ }
+ // First pass, flush any heavy paint events into real paint
+ // events. If there are pending heavy weight requests this will
+ // result in q'ing this request up one more time. As
+ // long as no other requests come in between now and the time
+ // the second one is processed nothing will happen. This is not
+ // ideal, but the logic needed to suppress the second request is
+ // more headache than it's worth.
+ scheduleHeavyWeightPaints();
+ // Do the actual validation and painting.
+ validateInvalidComponents();
+ prePaintDirtyRegions();
+ }
+ }
+ private RepaintManager getDelegate(Component c) {
+ RepaintManager delegate = SwingUtilities3.getDelegateRepaintManager(c);
+ if (this == delegate) {
+ delegate = null;
+ }
+ return delegate;
+ }
+}