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 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 Map volatileMap = new + HashMap(1); + + // + // As of 1.6 Swing handles scheduling of paint events from native code. + // That is, SwingPaintEventDispatcher is invoked on the toolkit thread, + // which in turn invokes nativeAddDirtyRegion. Because this is invoked + // from the native thread we can not invoke any public methods and so + // we introduce these added maps. So, any time nativeAddDirtyRegion is + // invoked the region is added to hwDirtyComponents and a work request + // is scheduled. When the work request is processed all entries in + // this map are pushed to the real map (dirtyComponents) and then + // painted with the rest of the components. + // + private Map hwDirtyComponents; + + private Map dirtyComponents; + private Map tmpDirtyComponents; + private java.util.List invalidComponents; + + // List of Runnables that need to be processed before painting from AWT. + private java.util.List runnableList; + + boolean doubleBufferingEnabled = true; + + private Dimension doubleBufferMaxSize; + + // Support for both the standard and volatile offscreen buffers exists to + // provide backwards compatibility for the [rare] programs which may be + // calling getOffScreenBuffer() and not expecting to get a VolatileImage. + // Swing internally is migrating to use *only* the volatile image buffer. + + // Support for standard offscreen buffer + // + DoubleBufferInfo standardDoubleBuffer; + + /** + * Object responsible for hanlding core paint functionality. + */ + private PaintManager paintManager; + + private static final Object repaintManagerKey = RepaintManager.class; + + // Whether or not a VolatileImage should be used for double-buffered painting + static boolean volatileImageBufferEnabled = true; + /** + * Type of VolatileImage which should be used for double-buffered + * painting. + */ + private static final int volatileBufferType; + /** + * Value of the system property awt.nativeDoubleBuffering. + */ + private static boolean nativeDoubleBuffering; + + // The maximum number of times Swing will attempt to use the VolatileImage + // buffer during a paint operation. + private static final int VOLATILE_LOOP_MAX = 2; + + /** + * Number of beginPaint 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. + *

+ * 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(); + tmpDirtyComponents = new IdentityHashMap(); + this.bufferStrategyType = bufferStrategyType; + hwDirtyComponents = new IdentityHashMap(); + } + processingRunnable = new ProcessingRunnable(); + } + + private void displayChanged() { + clearImages(); + } + + /** + * Mark the component as in need of layout and queue a runnable + * for the event dispatching thread that will validate the components + * first isValidateRoot() ancestor. + * + * @param invalidComponent a component + * @see JComponent#isValidateRoot + * @see #removeInvalidComponent + */ + public synchronized void addInvalidComponent(JComponent invalidComponent) + { + RepaintManager delegate = getDelegate(invalidComponent); + if (delegate != null) { + delegate.addInvalidComponent(invalidComponent); + return; + } + Component validateRoot = + SwingUtilities.getValidateRoot(invalidComponent, true); + + if (validateRoot == null) { + return; + } + + /* Lazily create the invalidateComponents vector and add the + * validateRoot if it's not there already. If this validateRoot + * is already in the vector, we're done. + */ + if (invalidComponents == null) { + invalidComponents = new ArrayList(); + } + else { + int n = invalidComponents.size(); + for(int i = 0; i < n; i++) { + if(validateRoot == invalidComponents.get(i)) { + return; + } + } + } + invalidComponents.add(validateRoot); + + // Queue a Runnable to invoke paintDirtyRegions and + // validateInvalidComponents. + scheduleProcessingRunnable(SunToolkit.targetToAppContext(invalidComponent)); + } + + + /** + * Remove a component from the list of invalid components. + * + * @param component a component + * @see #addInvalidComponent + */ + public synchronized void removeInvalidComponent(JComponent component) { + RepaintManager delegate = getDelegate(component); + if (delegate != null) { + delegate.removeInvalidComponent(component); + return; + } + if(invalidComponents != null) { + int index = invalidComponents.indexOf(component); + if(index != -1) { + invalidComponents.remove(index); + } + } + } + + + /** + * Add a component in the list of components that should be refreshed. + * If c already has a dirty region, the rectangle (x,y,w,h) + * will be unioned with the region that should be redrawn. + * + * @see JComponent#repaint + */ + @SuppressWarnings("deprecation") + private void addDirtyRegion0(Container c, int x, int y, int w, int h) { + /* Special cases we don't have to bother with. + */ + if ((w <= 0) || (h <= 0) || (c == null)) { + return; + } + + if ((c.getWidth() <= 0) || (c.getHeight() <= 0)) { + return; + } + + if (extendDirtyRegion(c, x, y, w, h)) { + // Component was already marked as dirty, region has been + // extended, no need to continue. + return; + } + + /* Make sure that c and all it ancestors (up to an Applet or + * Window) are visible. This loop has the same effect as + * checking c.isShowing() (and note that it's still possible + * that c is completely obscured by an opaque ancestor in + * the specified rectangle). + */ + Component root = null; + + // Note: We can't synchronize around this, Frame.getExtendedState + // is synchronized so that if we were to synchronize around this + // it could lead to the possibility of getting locks out + // of order and deadlocking. + for (Container p = c; p != null; p = p.getParent()) { + if (!p.isVisible() || !p.isDisplayable()) { + return; + } + if ((p instanceof Window) || (p instanceof Applet)) { + // Iconified frames are still visible! + if (p instanceof Frame && + (((Frame)p).getExtendedState() & Frame.ICONIFIED) == + Frame.ICONIFIED) { + return; + } + root = p; + break; + } + } + + if (root == null) return; + + synchronized(this) { + if (extendDirtyRegion(c, x, y, w, h)) { + // In between last check and this check another thread + // queued up runnable, can bail here. + return; + } + dirtyComponents.put(c, new Rectangle(x, y, w, h)); + } + + // Queue a Runnable to invoke paintDirtyRegions and + // validateInvalidComponents. + scheduleProcessingRunnable(SunToolkit.targetToAppContext(c)); + } + + /** + * Add a component in the list of components that should be refreshed. + * If c already has a dirty region, the rectangle (x,y,w,h) + * will be unioned with the region that should be redrawn. + * + * @param c Component 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 JComponent#repaint + */ + public void addDirtyRegion(JComponent c, int x, int y, int w, int h) + { + RepaintManager delegate = getDelegate(c); + if (delegate != null) { + delegate.addDirtyRegion(c, x, y, w, h); + return; + } + addDirtyRegion0(c, x, y, w, h); + } + + /** + * Adds window to the list of Components 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 Components 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() { + Map hws; + + synchronized(this) { + if (hwDirtyComponents.size() == 0) { + return; + } + hws = hwDirtyComponents; + hwDirtyComponents = new IdentityHashMap(); + } + for (Container hw : hws.keySet()) { + Rectangle dirty = hws.get(hw); + if (hw instanceof Window) { + addDirtyRegion((Window)hw, dirty.x, dirty.y, + dirty.width, dirty.height); + } + else if (hw instanceof Applet) { + addDirtyRegion((Applet)hw, dirty.x, dirty.y, + dirty.width, dirty.height); + } + else { // SwingHeavyWeight + addDirtyRegion0(hw, dirty.x, dirty.y, + dirty.width, dirty.height); + } + } + } + + // + // This is called from the toolkit thread when a native expose is + // received. + // + void nativeAddDirtyRegion(AppContext appContext, Container c, + int x, int y, int w, int h) { + if (w > 0 && h > 0) { + synchronized(this) { + Rectangle dirty = hwDirtyComponents.get(c); + if (dirty == null) { + hwDirtyComponents.put(c, new Rectangle(x, y, w, h)); + } + else { + hwDirtyComponents.put(c, SwingUtilities.computeUnion( + x, y, w, h, dirty)); + } + } + scheduleProcessingRunnable(appContext); + } + } + + // + // This is called from the toolkit thread when awt needs to run a + // Runnable before we paint. + // + void nativeQueueSurfaceDataRunnable(AppContext appContext, + final Component c, final Runnable r) + { + synchronized(this) { + if (runnableList == null) { + runnableList = new LinkedList(); + } + runnableList.add(new Runnable() { + public void run() { + AccessControlContext stack = AccessController.getContext(); + AccessControlContext acc = + AWTAccessor.getComponentAccessor().getAccessControlContext(c); + javaSecurityAccess.doIntersectionPrivilege(new PrivilegedAction() { + public Void run() { + r.run(); + return null; + } + }, stack, acc); + } + }); + } + scheduleProcessingRunnable(appContext); + } + + /** + * Extends the dirty region for the specified component to include + * the new region. + * + * @return false if c 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.List ic; + synchronized(this) { + if (invalidComponents == null) { + return; + } + ic = invalidComponents; + invalidComponents = null; + } + int n = ic.size(); + for(int i = 0; i < n; i++) { + final Component c = ic.get(i); + AccessControlContext stack = AccessController.getContext(); + AccessControlContext acc = + AWTAccessor.getComponentAccessor().getAccessControlContext(c); + javaSecurityAccess.doIntersectionPrivilege( + new PrivilegedAction() { + public Void run() { + c.validate(); + return null; + } + }, stack, acc); + } + } + + + /** + * This is invoked to process paint requests. It's needed + * for backward compatibility in so far as RepaintManager would previously + * not see paint requests for top levels, so, we have to make sure + * a subclass correctly paints any dirty top levels. + */ + private void prePaintDirtyRegions() { + Map dirtyComponents; + java.util.List runnableList; + synchronized(this) { + dirtyComponents = this.dirtyComponents; + runnableList = this.runnableList; + this.runnableList = null; + } + if (runnableList != null) { + for (Runnable runnable : runnableList) { + runnable.run(); + } + } + paintDirtyRegions(); + if (dirtyComponents.size() > 0) { + // This'll only happen if a subclass isn't correctly dealing + // with toplevels. + paintDirtyRegions(dirtyComponents); + } + } + + private void updateWindows(Map dirtyComponents) { + Toolkit toolkit = Toolkit.getDefaultToolkit(); + if (!(toolkit instanceof SunToolkit && + ((SunToolkit)toolkit).needUpdateWindow())) + { + return; + } + + Set windows = new HashSet(); + Set dirtyComps = dirtyComponents.keySet(); + for (Iterator it = dirtyComps.iterator(); it.hasNext();) { + Component dirty = it.next(); + Window window = dirty instanceof Window ? + (Window)dirty : + SwingUtilities.getWindowAncestor(dirty); + if (window != null && + !window.isOpaque()) + { + windows.add(window); + } + } + + for (Window window : windows) { + AWTAccessor.getWindowAccessor().updateWindow(window); + } + } + + boolean isPainting() { + return painting; + } + + /** + * Paint all of the components that have been marked dirty. + * + * @see #addDirtyRegion + */ + public void paintDirtyRegions() { + synchronized(this) { // swap for thread safety + Map tmp = tmpDirtyComponents; + tmpDirtyComponents = dirtyComponents; + dirtyComponents = tmp; + dirtyComponents.clear(); + } + paintDirtyRegions(tmpDirtyComponents); + } + + private void paintDirtyRegions( + final Map tmpDirtyComponents) + { + if (tmpDirtyComponents.isEmpty()) { + return; + } + + final java.util.List roots = + new ArrayList(tmpDirtyComponents.size()); + for (Component dirty : tmpDirtyComponents.keySet()) { + collectDirtyComponents(tmpDirtyComponents, dirty, roots); + } + + final AtomicInteger count = new AtomicInteger(roots.size()); + painting = true; + try { + for (int j=0 ; j < count.get(); j++) { + final int i = j; + final Component dirtyComponent = roots.get(j); + AccessControlContext stack = AccessController.getContext(); + AccessControlContext acc = + AWTAccessor.getComponentAccessor().getAccessControlContext(dirtyComponent); + javaSecurityAccess.doIntersectionPrivilege(new PrivilegedAction() { + public Void run() { + Rectangle rect = tmpDirtyComponents.get(dirtyComponent); + // Sometimes when RepaintManager is changed during the painting + // we may get null here, see #6995769 for details + if (rect == null) { + return null; + } + + int localBoundsH = dirtyComponent.getHeight(); + int localBoundsW = dirtyComponent.getWidth(); + SwingUtilities.computeIntersection(0, + 0, + localBoundsW, + localBoundsH, + rect); + if (dirtyComponent instanceof JComponent) { + ((JComponent)dirtyComponent).paintImmediately( + rect.x,rect.y,rect.width, rect.height); + } + else if (dirtyComponent.isShowing()) { + Graphics g = JComponent.safelyGetGraphics( + dirtyComponent, dirtyComponent); + // If the Graphics goes away, it means someone disposed of + // the window, don't do anything. + if (g != null) { + g.setClip(rect.x, rect.y, rect.width, rect.height); + try { + dirtyComponent.paint(g); + } finally { + g.dispose(); + } + } + } + // If the repaintRoot has been set, service it now and + // remove any components that are children of repaintRoot. + if (repaintRoot != null) { + adjustRoots(repaintRoot, roots, i + 1); + count.set(roots.size()); + paintManager.isRepaintingRoot = true; + repaintRoot.paintImmediately(0, 0, repaintRoot.getWidth(), + repaintRoot.getHeight()); + paintManager.isRepaintingRoot = false; + // Only service repaintRoot once. + repaintRoot = null; + } + + return null; + } + }, stack, acc); + } + } finally { + painting = false; + } + + updateWindows(tmpDirtyComponents); + + tmpDirtyComponents.clear(); + } + + + /** + * Removes any components from roots that are children of + * root. + */ + private void adjustRoots(JComponent root, + java.util.List roots, int index) { + for (int i = roots.size() - 1; i >= index; i--) { + Component c = roots.get(i); + for(;;) { + if (c == root || c == null || !(c instanceof JComponent)) { + break; + } + c = c.getParent(); + } + if (c == root) { + roots.remove(i); + } + } + } + + Rectangle tmp = new Rectangle(); + + void collectDirtyComponents(Map dirtyComponents, + Component dirtyComponent, + java.util.List roots) { + int dx, dy, rootDx, rootDy; + Component component, rootDirtyComponent,parent; + Rectangle cBounds; + + // Find the highest parent which is dirty. When we get out of this + // rootDx and rootDy will contain the translation from the + // rootDirtyComponent's coordinate system to the coordinates of the + // original dirty component. The tmp Rect is also used to compute the + // visible portion of the dirtyRect. + + component = rootDirtyComponent = dirtyComponent; + + int x = dirtyComponent.getX(); + int y = dirtyComponent.getY(); + int w = dirtyComponent.getWidth(); + int h = dirtyComponent.getHeight(); + + dx = rootDx = 0; + dy = rootDy = 0; + tmp.setBounds(dirtyComponents.get(dirtyComponent)); + + // System.out.println("Collect dirty component for bound " + tmp + + // "component bounds is " + cBounds);; + SwingUtilities.computeIntersection(0,0,w,h,tmp); + + if (tmp.isEmpty()) { + // System.out.println("Empty 1"); + return; + } + + for(;;) { + if(!(component instanceof JComponent)) + break; + + parent = component.getParent(); + if(parent == null) + break; + + component = parent; + + dx += x; + dy += y; + tmp.setLocation(tmp.x + x, tmp.y + y); + + x = component.getX(); + y = component.getY(); + w = component.getWidth(); + h = component.getHeight(); + tmp = SwingUtilities.computeIntersection(0,0,w,h,tmp); + + if (tmp.isEmpty()) { + // System.out.println("Empty 2"); + return; + } + + if (dirtyComponents.get(component) != null) { + rootDirtyComponent = component; + rootDx = dx; + rootDy = dy; + } + } + + if (dirtyComponent != rootDirtyComponent) { + Rectangle r; + tmp.setLocation(tmp.x + rootDx - dx, + tmp.y + rootDy - dy); + r = dirtyComponents.get(rootDirtyComponent); + SwingUtilities.computeUnion(tmp.x,tmp.y,tmp.width,tmp.height,r); + } + + // If we haven't seen this root before, then we need to add it to the + // list of root dirty Views. + + if (!roots.contains(rootDirtyComponent)) + roots.add(rootDirtyComponent); + } + + + /** + * Returns a string that displays and identifies this + * object's properties. + * + * @return a String representation of this object + */ + public synchronized String toString() { + StringBuilder sb = new StringBuilder(); + if(dirtyComponents != null) + sb.append("" + dirtyComponents); + return sb.toString(); + } + + + /** + * Return the offscreen buffer that should be used as a double buffer with + * the component c. + * 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 + Iterator gcs = volatileMap.keySet().iterator(); + while (gcs.hasNext()) { + GraphicsConfiguration gc = gcs.next(); + VolatileImage image = volatileMap.get(gc); + if (image.getWidth() > width || image.getHeight() > height) { + image.flush(); + gcs.remove(); + } + } + } + + /** + * Returns the maximum double buffer size. + * + * @return a Dimension object representing the maximum size + */ + public Dimension getDoubleBufferMaximumSize() { + if (doubleBufferMaxSize == null) { + try { + Rectangle virtualBounds = new Rectangle(); + GraphicsEnvironment ge = GraphicsEnvironment. + getLocalGraphicsEnvironment(); + for (GraphicsDevice gd : ge.getScreenDevices()) { + GraphicsConfiguration gc = gd.getDefaultConfiguration(); + virtualBounds = virtualBounds.union(gc.getBounds()); + } + doubleBufferMaxSize = new Dimension(virtualBounds.width, + virtualBounds.height); + } catch (HeadlessException e) { + doubleBufferMaxSize = new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); + } + } + return doubleBufferMaxSize; + } + + /** + * Enables or disables double buffering in this RepaintManager. + * CAUTION: The default value for this property is set for optimal + * paint performance on the given platform and it is not recommended + * that programs modify this property directly. + * + * @param aFlag true to activate double buffering + * @see #isDoubleBufferingEnabled + */ + public void setDoubleBufferingEnabled(boolean aFlag) { + doubleBufferingEnabled = aFlag; + PaintManager paintManager = getPaintManager(); + if (!aFlag && paintManager.getClass() != PaintManager.class) { + setPaintManager(new PaintManager()); + } + } + + /** + * Returns true if this RepaintManager is double buffered. + * The default value for this property may vary from platform + * to platform. On platforms where native double buffering + * is supported in the AWT, the default value will be false + * 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.List repaintListeners = new ArrayList<>(1); + + private void addRepaintListener(RepaintListener l) { + repaintListeners.add(l); + } + + private void removeRepaintListener(RepaintListener l) { + repaintListeners.remove(l); + } + + /** + * Notify the attached repaint listeners that an area of the {@code c} component + * has been immediately repainted, that is without scheduling a repaint runnable, + * due to performing a "blit" (via calling the {@code copyArea} method). + * + * @param c the component + * @param x the x coordinate of the area + * @param y the y coordinate of the area + * @param w the width of the area + * @param h the height of the area + */ + void notifyRepaintPerformed(JComponent c, int x, int y, int w, int h) { + for (RepaintListener l : repaintListeners) { + l.repaintPerformed(c, x, y, w, h); + } + } + + /** + * Invoked prior to any paint/copyArea method calls. This will + * be followed by an invocation of endPaint. + * 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. + *

+ * 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 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; + } +}