1 /*
   2  * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing;
  26 
  27 
  28 import java.awt.*;
  29 import java.awt.event.*;
  30 import java.awt.image.VolatileImage;
  31 import java.security.AccessControlContext;
  32 import java.security.AccessController;
  33 import java.security.PrivilegedAction;
  34 import java.util.*;
  35 import java.util.concurrent.atomic.AtomicInteger;
  36 import java.applet.*;
  37 
  38 import jdk.internal.misc.JavaSecurityAccess;
  39 import jdk.internal.misc.SharedSecrets;
  40 import sun.awt.AWTAccessor;
  41 import sun.awt.AppContext;
  42 import sun.awt.DisplayChangedListener;
  43 import sun.awt.SunToolkit;
  44 import sun.java2d.SunGraphicsEnvironment;
  45 import sun.security.action.GetPropertyAction;
  46 
  47 import com.sun.java.swing.SwingUtilities3;
  48 import java.awt.geom.AffineTransform;
  49 import sun.java2d.SunGraphics2D;
  50 import sun.java2d.pipe.Region;
  51 import sun.swing.SwingAccessor;
  52 import sun.swing.SwingUtilities2;
  53 import sun.swing.SwingUtilities2.RepaintListener;
  54 
  55 /**
  56  * This class manages repaint requests, allowing the number
  57  * of repaints to be minimized, for example by collapsing multiple
  58  * requests into a single repaint for members of a component tree.
  59  * <p>
  60  * As of 1.6 <code>RepaintManager</code> handles repaint requests
  61  * for Swing's top level components (<code>JApplet</code>,
  62  * <code>JWindow</code>, <code>JFrame</code> and <code>JDialog</code>).
  63  * Any calls to <code>repaint</code> on one of these will call into the
  64  * appropriate <code>addDirtyRegion</code> method.
  65  *
  66  * @author Arnaud Weber
  67  * @since 1.2
  68  */
  69 public class RepaintManager
  70 {
  71     /**
  72      * Whether or not the RepaintManager should handle paint requests
  73      * for top levels.
  74      */
  75     static final boolean HANDLE_TOP_LEVEL_PAINT;
  76 
  77     private static final short BUFFER_STRATEGY_NOT_SPECIFIED = 0;
  78     private static final short BUFFER_STRATEGY_SPECIFIED_ON = 1;
  79     private static final short BUFFER_STRATEGY_SPECIFIED_OFF = 2;
  80 
  81     private static final short BUFFER_STRATEGY_TYPE;
  82 
  83     /**
  84      * Maps from GraphicsConfiguration to VolatileImage.
  85      */
  86     private Map<GraphicsConfiguration,VolatileImage> volatileMap = new
  87                         HashMap<GraphicsConfiguration,VolatileImage>(1);
  88 
  89     //
  90     // As of 1.6 Swing handles scheduling of paint events from native code.
  91     // That is, SwingPaintEventDispatcher is invoked on the toolkit thread,
  92     // which in turn invokes nativeAddDirtyRegion.  Because this is invoked
  93     // from the native thread we can not invoke any public methods and so
  94     // we introduce these added maps.  So, any time nativeAddDirtyRegion is
  95     // invoked the region is added to hwDirtyComponents and a work request
  96     // is scheduled.  When the work request is processed all entries in
  97     // this map are pushed to the real map (dirtyComponents) and then
  98     // painted with the rest of the components.
  99     //
 100     private Map<Container,Rectangle> hwDirtyComponents;
 101 
 102     private Map<Component,Rectangle> dirtyComponents;
 103     private Map<Component,Rectangle> tmpDirtyComponents;
 104     private java.util.List<Component> invalidComponents;
 105 
 106     // List of Runnables that need to be processed before painting from AWT.
 107     private java.util.List<Runnable> runnableList;
 108 
 109     boolean   doubleBufferingEnabled = true;
 110 
 111     private Dimension doubleBufferMaxSize;
 112 
 113     private boolean isCustomMaxBufferSizeSet = false;
 114 
 115     // Support for both the standard and volatile offscreen buffers exists to
 116     // provide backwards compatibility for the [rare] programs which may be
 117     // calling getOffScreenBuffer() and not expecting to get a VolatileImage.
 118     // Swing internally is migrating to use *only* the volatile image buffer.
 119 
 120     // Support for standard offscreen buffer
 121     //
 122     DoubleBufferInfo standardDoubleBuffer;
 123 
 124     /**
 125      * Object responsible for hanlding core paint functionality.
 126      */
 127     private PaintManager paintManager;
 128 
 129     private static final Object repaintManagerKey = RepaintManager.class;
 130 
 131     // Whether or not a VolatileImage should be used for double-buffered painting
 132     static boolean volatileImageBufferEnabled = true;
 133     /**
 134      * Type of VolatileImage which should be used for double-buffered
 135      * painting.
 136      */
 137     private static final int volatileBufferType;
 138     /**
 139      * Value of the system property awt.nativeDoubleBuffering.
 140      */
 141     private static boolean nativeDoubleBuffering;
 142 
 143     // The maximum number of times Swing will attempt to use the VolatileImage
 144     // buffer during a paint operation.
 145     private static final int VOLATILE_LOOP_MAX = 2;
 146 
 147     /**
 148      * Number of <code>beginPaint</code> that have been invoked.
 149      */
 150     private int paintDepth = 0;
 151 
 152     /**
 153      * Type of buffer strategy to use.  Will be one of the BUFFER_STRATEGY_
 154      * constants.
 155      */
 156     private short bufferStrategyType;
 157 
 158     //
 159     // BufferStrategyPaintManager has the unique characteristic that it
 160     // must deal with the buffer being lost while painting to it.  For
 161     // example, if we paint a component and show it and the buffer has
 162     // become lost we must repaint the whole window.  To deal with that
 163     // the PaintManager calls into repaintRoot, and if we're still in
 164     // the process of painting the repaintRoot field is set to the JRootPane
 165     // and after the current JComponent.paintImmediately call finishes
 166     // paintImmediately will be invoked on the repaintRoot.  In this
 167     // way we don't try to show garbage to the screen.
 168     //
 169     /**
 170      * True if we're in the process of painting the dirty regions.  This is
 171      * set to true in <code>paintDirtyRegions</code>.
 172      */
 173     private boolean painting;
 174     /**
 175      * If the PaintManager calls into repaintRoot during painting this field
 176      * will be set to the root.
 177      */
 178     private JComponent repaintRoot;
 179 
 180     /**
 181      * The Thread that has initiated painting.  If null it
 182      * indicates painting is not currently in progress.
 183      */
 184     private Thread paintThread;
 185 
 186     /**
 187      * Runnable used to process all repaint/revalidate requests.
 188      */
 189     private final ProcessingRunnable processingRunnable;
 190 
 191     private static final JavaSecurityAccess javaSecurityAccess =
 192             SharedSecrets.getJavaSecurityAccess();
 193 
 194     /**
 195      * Listener installed to detect display changes. When display changes,
 196      * schedules a callback to notify all RepaintManagers of the display
 197      * changes.
 198      */
 199     private static final DisplayChangedListener displayChangedHandler =
 200             new DisplayChangedHandler();
 201 
 202     static {
 203         SwingAccessor.setRepaintManagerAccessor(new SwingAccessor.RepaintManagerAccessor() {
 204             @Override
 205             public void addRepaintListener(RepaintManager rm, RepaintListener l) {
 206                 rm.addRepaintListener(l);
 207             }
 208             @Override
 209             public void removeRepaintListener(RepaintManager rm, RepaintListener l) {
 210                 rm.removeRepaintListener(l);
 211             }
 212         });
 213 
 214         volatileImageBufferEnabled = "true".equals(AccessController.
 215                 doPrivileged(new GetPropertyAction(
 216                 "swing.volatileImageBufferEnabled", "true")));
 217         boolean headless = GraphicsEnvironment.isHeadless();
 218         if (volatileImageBufferEnabled && headless) {
 219             volatileImageBufferEnabled = false;
 220         }
 221         nativeDoubleBuffering = "true".equals(AccessController.doPrivileged(
 222                     new GetPropertyAction("awt.nativeDoubleBuffering")));
 223         String bs = AccessController.doPrivileged(
 224                           new GetPropertyAction("swing.bufferPerWindow"));
 225         if (headless) {
 226             BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_SPECIFIED_OFF;
 227         }
 228         else if (bs == null) {
 229             BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_NOT_SPECIFIED;
 230         }
 231         else if ("true".equals(bs)) {
 232             BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_SPECIFIED_ON;
 233         }
 234         else {
 235             BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_SPECIFIED_OFF;
 236         }
 237         HANDLE_TOP_LEVEL_PAINT = "true".equals(AccessController.doPrivileged(
 238                new GetPropertyAction("swing.handleTopLevelPaint", "true")));
 239         GraphicsEnvironment ge = GraphicsEnvironment.
 240                 getLocalGraphicsEnvironment();
 241         if (ge instanceof SunGraphicsEnvironment) {
 242             ((SunGraphicsEnvironment) ge).addDisplayChangedListener(
 243                     displayChangedHandler);
 244         }
 245         Toolkit tk = Toolkit.getDefaultToolkit();
 246         if ((tk instanceof SunToolkit)
 247                 && ((SunToolkit) tk).isSwingBackbufferTranslucencySupported()) {
 248             volatileBufferType = Transparency.TRANSLUCENT;
 249         } else {
 250             volatileBufferType = Transparency.OPAQUE;
 251         }
 252     }
 253 
 254     /**
 255      * Return the RepaintManager for the calling thread given a Component.
 256      *
 257      * @param c a Component -- unused in the default implementation, but could
 258      *          be used by an overridden version to return a different RepaintManager
 259      *          depending on the Component
 260      * @return the RepaintManager object
 261      */
 262     public static RepaintManager currentManager(Component c) {
 263         // Note: DisplayChangedRunnable passes in null as the component, so if
 264         // component is ever used to determine the current
 265         // RepaintManager, DisplayChangedRunnable will need to be modified
 266         // accordingly.
 267         return currentManager(AppContext.getAppContext());
 268     }
 269 
 270     /**
 271      * Returns the RepaintManager for the specified AppContext.  If
 272      * a RepaintManager has not been created for the specified
 273      * AppContext this will return null.
 274      */
 275     static RepaintManager currentManager(AppContext appContext) {
 276         RepaintManager rm = (RepaintManager)appContext.get(repaintManagerKey);
 277         if (rm == null) {
 278             rm = new RepaintManager(BUFFER_STRATEGY_TYPE);
 279             appContext.put(repaintManagerKey, rm);
 280         }
 281         return rm;
 282     }
 283 
 284     /**
 285      * Return the RepaintManager for the calling thread given a JComponent.
 286      * <p>
 287     * Note: This method exists for backward binary compatibility with earlier
 288      * versions of the Swing library. It simply returns the result returned by
 289      * {@link #currentManager(Component)}.
 290      *
 291      * @param c a JComponent -- unused
 292      * @return the RepaintManager object
 293      */
 294     public static RepaintManager currentManager(JComponent c) {
 295         return currentManager((Component)c);
 296     }
 297 
 298 
 299     /**
 300      * Set the RepaintManager that should be used for the calling
 301      * thread. <b>aRepaintManager</b> will become the current RepaintManager
 302      * for the calling thread's thread group.
 303      * @param aRepaintManager  the RepaintManager object to use
 304      */
 305     public static void setCurrentManager(RepaintManager aRepaintManager) {
 306         if (aRepaintManager != null) {
 307             SwingUtilities.appContextPut(repaintManagerKey, aRepaintManager);
 308         } else {
 309             SwingUtilities.appContextRemove(repaintManagerKey);
 310         }
 311     }
 312 
 313     /**
 314      * Create a new RepaintManager instance. You rarely call this constructor.
 315      * directly. To get the default RepaintManager, use
 316      * RepaintManager.currentManager(JComponent) (normally "this").
 317      */
 318     public RepaintManager() {
 319         // Because we can't know what a subclass is doing with the
 320         // volatile image we immediately punt in subclasses.  If this
 321         // poses a problem we'll need a more sophisticated detection algorithm,
 322         // or API.
 323         this(BUFFER_STRATEGY_SPECIFIED_OFF);
 324     }
 325 
 326     private RepaintManager(short bufferStrategyType) {
 327         // If native doublebuffering is being used, do NOT use
 328         // Swing doublebuffering.
 329         doubleBufferingEnabled = !nativeDoubleBuffering;
 330         synchronized(this) {
 331             dirtyComponents = new IdentityHashMap<Component,Rectangle>();
 332             tmpDirtyComponents = new IdentityHashMap<Component,Rectangle>();
 333             this.bufferStrategyType = bufferStrategyType;
 334             hwDirtyComponents = new IdentityHashMap<Container,Rectangle>();
 335         }
 336         processingRunnable = new ProcessingRunnable();
 337     }
 338 
 339     private void displayChanged() {
 340         if (isCustomMaxBufferSizeSet) {
 341             clearImages();
 342         } else {
 343             // Reset buffer maximum size to get valid size from updated graphics
 344             // environment in getDoubleBufferMaximumSize()
 345             setDoubleBufferMaximumSize(null);
 346         }
 347     }
 348 
 349     /**
 350      * Mark the component as in need of layout and queue a runnable
 351      * for the event dispatching thread that will validate the components
 352      * first isValidateRoot() ancestor.
 353      *
 354      * @param invalidComponent a component
 355      * @see JComponent#isValidateRoot
 356      * @see #removeInvalidComponent
 357      */
 358     public synchronized void addInvalidComponent(JComponent invalidComponent)
 359     {
 360         RepaintManager delegate = getDelegate(invalidComponent);
 361         if (delegate != null) {
 362             delegate.addInvalidComponent(invalidComponent);
 363             return;
 364         }
 365         Component validateRoot =
 366             SwingUtilities.getValidateRoot(invalidComponent, true);
 367 
 368         if (validateRoot == null) {
 369             return;
 370         }
 371 
 372         /* Lazily create the invalidateComponents vector and add the
 373          * validateRoot if it's not there already.  If this validateRoot
 374          * is already in the vector, we're done.
 375          */
 376         if (invalidComponents == null) {
 377             invalidComponents = new ArrayList<Component>();
 378         }
 379         else {
 380             int n = invalidComponents.size();
 381             for(int i = 0; i < n; i++) {
 382                 if(validateRoot == invalidComponents.get(i)) {
 383                     return;
 384                 }
 385             }
 386         }
 387         invalidComponents.add(validateRoot);
 388 
 389         // Queue a Runnable to invoke paintDirtyRegions and
 390         // validateInvalidComponents.
 391         scheduleProcessingRunnable(SunToolkit.targetToAppContext(invalidComponent));
 392     }
 393 
 394 
 395     /**
 396      * Remove a component from the list of invalid components.
 397      *
 398      * @param component a component
 399      * @see #addInvalidComponent
 400      */
 401     public synchronized void removeInvalidComponent(JComponent component) {
 402         RepaintManager delegate = getDelegate(component);
 403         if (delegate != null) {
 404             delegate.removeInvalidComponent(component);
 405             return;
 406         }
 407         if(invalidComponents != null) {
 408             int index = invalidComponents.indexOf(component);
 409             if(index != -1) {
 410                 invalidComponents.remove(index);
 411             }
 412         }
 413     }
 414 
 415 
 416     /**
 417      * Add a component in the list of components that should be refreshed.
 418      * If <i>c</i> already has a dirty region, the rectangle <i>(x,y,w,h)</i>
 419      * will be unioned with the region that should be redrawn.
 420      *
 421      * @see JComponent#repaint
 422      */
 423     @SuppressWarnings("deprecation")
 424     private void addDirtyRegion0(Container c, int x, int y, int w, int h) {
 425         /* Special cases we don't have to bother with.
 426          */
 427         if ((w <= 0) || (h <= 0) || (c == null)) {
 428             return;
 429         }
 430 
 431         if ((c.getWidth() <= 0) || (c.getHeight() <= 0)) {
 432             return;
 433         }
 434 
 435         if (extendDirtyRegion(c, x, y, w, h)) {
 436             // Component was already marked as dirty, region has been
 437             // extended, no need to continue.
 438             return;
 439         }
 440 
 441         /* Make sure that c and all it ancestors (up to an Applet or
 442          * Window) are visible.  This loop has the same effect as
 443          * checking c.isShowing() (and note that it's still possible
 444          * that c is completely obscured by an opaque ancestor in
 445          * the specified rectangle).
 446          */
 447         Component root = null;
 448 
 449         // Note: We can't synchronize around this, Frame.getExtendedState
 450         // is synchronized so that if we were to synchronize around this
 451         // it could lead to the possibility of getting locks out
 452         // of order and deadlocking.
 453         for (Container p = c; p != null; p = p.getParent()) {
 454             if (!p.isVisible() || !p.isDisplayable()) {
 455                 return;
 456             }
 457             if ((p instanceof Window) || (p instanceof Applet)) {
 458                 // Iconified frames are still visible!
 459                 if (p instanceof Frame &&
 460                         (((Frame)p).getExtendedState() & Frame.ICONIFIED) ==
 461                                     Frame.ICONIFIED) {
 462                     return;
 463                 }
 464                 root = p;
 465                 break;
 466             }
 467         }
 468 
 469         if (root == null) return;
 470 
 471         synchronized(this) {
 472             if (extendDirtyRegion(c, x, y, w, h)) {
 473                 // In between last check and this check another thread
 474                 // queued up runnable, can bail here.
 475                 return;
 476             }
 477             dirtyComponents.put(c, new Rectangle(x, y, w, h));
 478         }
 479 
 480         // Queue a Runnable to invoke paintDirtyRegions and
 481         // validateInvalidComponents.
 482         scheduleProcessingRunnable(SunToolkit.targetToAppContext(c));
 483     }
 484 
 485     /**
 486      * Add a component in the list of components that should be refreshed.
 487      * If <i>c</i> already has a dirty region, the rectangle <i>(x,y,w,h)</i>
 488      * will be unioned with the region that should be redrawn.
 489      *
 490      * @param c Component to repaint, null results in nothing happening.
 491      * @param x X coordinate of the region to repaint
 492      * @param y Y coordinate of the region to repaint
 493      * @param w Width of the region to repaint
 494      * @param h Height of the region to repaint
 495      * @see JComponent#repaint
 496      */
 497     public void addDirtyRegion(JComponent c, int x, int y, int w, int h)
 498     {
 499         RepaintManager delegate = getDelegate(c);
 500         if (delegate != null) {
 501             delegate.addDirtyRegion(c, x, y, w, h);
 502             return;
 503         }
 504         addDirtyRegion0(c, x, y, w, h);
 505     }
 506 
 507     /**
 508      * Adds <code>window</code> to the list of <code>Component</code>s that
 509      * need to be repainted.
 510      *
 511      * @param window Window to repaint, null results in nothing happening.
 512      * @param x X coordinate of the region to repaint
 513      * @param y Y coordinate of the region to repaint
 514      * @param w Width of the region to repaint
 515      * @param h Height of the region to repaint
 516      * @see JFrame#repaint
 517      * @see JWindow#repaint
 518      * @see JDialog#repaint
 519      * @since 1.6
 520      */
 521     public void addDirtyRegion(Window window, int x, int y, int w, int h) {
 522         addDirtyRegion0(window, x, y, w, h);
 523     }
 524 
 525     /**
 526      * Adds <code>applet</code> to the list of <code>Component</code>s that
 527      * need to be repainted.
 528      *
 529      * @param applet Applet to repaint, null results in nothing happening.
 530      * @param x X coordinate of the region to repaint
 531      * @param y Y coordinate of the region to repaint
 532      * @param w Width of the region to repaint
 533      * @param h Height of the region to repaint
 534      * @see JApplet#repaint
 535      * @since 1.6
 536      *
 537      * @deprecated The Applet API is deprecated. See the
 538      * <a href="../../java/applet/package-summary.html"> java.applet package
 539      * documentation</a> for further information.
 540      */
 541     @Deprecated(since = "9")
 542     public void addDirtyRegion(Applet applet, int x, int y, int w, int h) {
 543         addDirtyRegion0(applet, x, y, w, h);
 544     }
 545 
 546     @SuppressWarnings("deprecation")
 547     void scheduleHeavyWeightPaints() {
 548         Map<Container,Rectangle> hws;
 549 
 550         synchronized(this) {
 551             if (hwDirtyComponents.size() == 0) {
 552                 return;
 553             }
 554             hws = hwDirtyComponents;
 555             hwDirtyComponents =  new IdentityHashMap<Container,Rectangle>();
 556         }
 557         for (Container hw : hws.keySet()) {
 558             Rectangle dirty = hws.get(hw);
 559             if (hw instanceof Window) {
 560                 addDirtyRegion((Window)hw, dirty.x, dirty.y,
 561                                dirty.width, dirty.height);
 562             }
 563             else if (hw instanceof Applet) {
 564                 addDirtyRegion((Applet)hw, dirty.x, dirty.y,
 565                                dirty.width, dirty.height);
 566             }
 567             else { // SwingHeavyWeight
 568                 addDirtyRegion0(hw, dirty.x, dirty.y,
 569                                 dirty.width, dirty.height);
 570             }
 571         }
 572     }
 573 
 574     //
 575     // This is called from the toolkit thread when a native expose is
 576     // received.
 577     //
 578     void nativeAddDirtyRegion(AppContext appContext, Container c,
 579                               int x, int y, int w, int h) {
 580         if (w > 0 && h > 0) {
 581             synchronized(this) {
 582                 Rectangle dirty = hwDirtyComponents.get(c);
 583                 if (dirty == null) {
 584                     hwDirtyComponents.put(c, new Rectangle(x, y, w, h));
 585                 }
 586                 else {
 587                     hwDirtyComponents.put(c, SwingUtilities.computeUnion(
 588                                               x, y, w, h, dirty));
 589                 }
 590             }
 591             scheduleProcessingRunnable(appContext);
 592         }
 593     }
 594 
 595     //
 596     // This is called from the toolkit thread when awt needs to run a
 597     // Runnable before we paint.
 598     //
 599     void nativeQueueSurfaceDataRunnable(AppContext appContext,
 600                                         final Component c, final Runnable r)
 601     {
 602         synchronized(this) {
 603             if (runnableList == null) {
 604                 runnableList = new LinkedList<Runnable>();
 605             }
 606             runnableList.add(new Runnable() {
 607                 public void run() {
 608                     AccessControlContext stack = AccessController.getContext();
 609                     AccessControlContext acc =
 610                         AWTAccessor.getComponentAccessor().getAccessControlContext(c);
 611                     javaSecurityAccess.doIntersectionPrivilege(new PrivilegedAction<Void>() {
 612                         public Void run() {
 613                             r.run();
 614                             return null;
 615                         }
 616                     }, stack, acc);
 617                 }
 618             });
 619         }
 620         scheduleProcessingRunnable(appContext);
 621     }
 622 
 623     /**
 624      * Extends the dirty region for the specified component to include
 625      * the new region.
 626      *
 627      * @return false if <code>c</code> is not yet marked dirty.
 628      */
 629     private synchronized boolean extendDirtyRegion(
 630         Component c, int x, int y, int w, int h) {
 631         Rectangle r = dirtyComponents.get(c);
 632         if (r != null) {
 633             // A non-null r implies c is already marked as dirty,
 634             // and that the parent is valid. Therefore we can
 635             // just union the rect and bail.
 636             SwingUtilities.computeUnion(x, y, w, h, r);
 637             return true;
 638         }
 639         return false;
 640     }
 641 
 642     /**
 643      * Return the current dirty region for a component.
 644      * Return an empty rectangle if the component is not
 645      * dirty.
 646      *
 647      * @param aComponent a component
 648      * @return the region
 649      */
 650     public Rectangle getDirtyRegion(JComponent aComponent) {
 651         RepaintManager delegate = getDelegate(aComponent);
 652         if (delegate != null) {
 653             return delegate.getDirtyRegion(aComponent);
 654         }
 655         Rectangle r;
 656         synchronized(this) {
 657             r = dirtyComponents.get(aComponent);
 658         }
 659         if(r == null)
 660             return new Rectangle(0,0,0,0);
 661         else
 662             return new Rectangle(r);
 663     }
 664 
 665     /**
 666      * Mark a component completely dirty. <b>aComponent</b> will be
 667      * completely painted during the next paintDirtyRegions() call.
 668      *
 669      * @param aComponent a component
 670      */
 671     public void markCompletelyDirty(JComponent aComponent) {
 672         RepaintManager delegate = getDelegate(aComponent);
 673         if (delegate != null) {
 674             delegate.markCompletelyDirty(aComponent);
 675             return;
 676         }
 677         addDirtyRegion(aComponent,0,0,Integer.MAX_VALUE,Integer.MAX_VALUE);
 678     }
 679 
 680     /**
 681      * Mark a component completely clean. <b>aComponent</b> will not
 682      * get painted during the next paintDirtyRegions() call.
 683      *
 684      * @param aComponent a component
 685      */
 686     public void markCompletelyClean(JComponent aComponent) {
 687         RepaintManager delegate = getDelegate(aComponent);
 688         if (delegate != null) {
 689             delegate.markCompletelyClean(aComponent);
 690             return;
 691         }
 692         synchronized(this) {
 693                 dirtyComponents.remove(aComponent);
 694         }
 695     }
 696 
 697     /**
 698      * Convenience method that returns true if <b>aComponent</b> will be completely
 699      * painted during the next paintDirtyRegions(). If computing dirty regions is
 700      * expensive for your component, use this method and avoid computing dirty region
 701      * if it return true.
 702      *
 703      * @param aComponent a component
 704      * @return {@code true} if <b>aComponent</b> will be completely
 705      *         painted during the next paintDirtyRegions().
 706      */
 707     public boolean isCompletelyDirty(JComponent aComponent) {
 708         RepaintManager delegate = getDelegate(aComponent);
 709         if (delegate != null) {
 710             return delegate.isCompletelyDirty(aComponent);
 711         }
 712         Rectangle r;
 713 
 714         r = getDirtyRegion(aComponent);
 715         if(r.width == Integer.MAX_VALUE &&
 716            r.height == Integer.MAX_VALUE)
 717             return true;
 718         else
 719             return false;
 720     }
 721 
 722 
 723     /**
 724      * Validate all of the components that have been marked invalid.
 725      * @see #addInvalidComponent
 726      */
 727     public void validateInvalidComponents() {
 728         final java.util.List<Component> ic;
 729         synchronized(this) {
 730             if (invalidComponents == null) {
 731                 return;
 732             }
 733             ic = invalidComponents;
 734             invalidComponents = null;
 735         }
 736         int n = ic.size();
 737         for(int i = 0; i < n; i++) {
 738             final Component c = ic.get(i);
 739             AccessControlContext stack = AccessController.getContext();
 740             AccessControlContext acc =
 741                 AWTAccessor.getComponentAccessor().getAccessControlContext(c);
 742             javaSecurityAccess.doIntersectionPrivilege(
 743                 new PrivilegedAction<Void>() {
 744                     public Void run() {
 745                         c.validate();
 746                         return null;
 747                     }
 748                 }, stack, acc);
 749         }
 750     }
 751 
 752 
 753     /**
 754      * This is invoked to process paint requests.  It's needed
 755      * for backward compatibility in so far as RepaintManager would previously
 756      * not see paint requests for top levels, so, we have to make sure
 757      * a subclass correctly paints any dirty top levels.
 758      */
 759     private void prePaintDirtyRegions() {
 760         Map<Component,Rectangle> dirtyComponents;
 761         java.util.List<Runnable> runnableList;
 762         synchronized(this) {
 763             dirtyComponents = this.dirtyComponents;
 764             runnableList = this.runnableList;
 765             this.runnableList = null;
 766         }
 767         if (runnableList != null) {
 768             for (Runnable runnable : runnableList) {
 769                 runnable.run();
 770             }
 771         }
 772         paintDirtyRegions();
 773         if (dirtyComponents.size() > 0) {
 774             // This'll only happen if a subclass isn't correctly dealing
 775             // with toplevels.
 776             paintDirtyRegions(dirtyComponents);
 777         }
 778     }
 779 
 780     private void updateWindows(Map<Component,Rectangle> dirtyComponents) {
 781         Toolkit toolkit = Toolkit.getDefaultToolkit();
 782         if (!(toolkit instanceof SunToolkit &&
 783               ((SunToolkit)toolkit).needUpdateWindow()))
 784         {
 785             return;
 786         }
 787 
 788         Set<Window> windows = new HashSet<Window>();
 789         Set<Component> dirtyComps = dirtyComponents.keySet();
 790         for (Iterator<Component> it = dirtyComps.iterator(); it.hasNext();) {
 791             Component dirty = it.next();
 792             Window window = dirty instanceof Window ?
 793                 (Window)dirty :
 794                 SwingUtilities.getWindowAncestor(dirty);
 795             if (window != null &&
 796                 !window.isOpaque())
 797             {
 798                 windows.add(window);
 799             }
 800         }
 801 
 802         for (Window window : windows) {
 803             AWTAccessor.getWindowAccessor().updateWindow(window);
 804         }
 805     }
 806 
 807     boolean isPainting() {
 808         return painting;
 809     }
 810 
 811     /**
 812      * Paint all of the components that have been marked dirty.
 813      *
 814      * @see #addDirtyRegion
 815      */
 816     public void paintDirtyRegions() {
 817         synchronized(this) {  // swap for thread safety
 818             Map<Component,Rectangle> tmp = tmpDirtyComponents;
 819             tmpDirtyComponents = dirtyComponents;
 820             dirtyComponents = tmp;
 821             dirtyComponents.clear();
 822         }
 823         paintDirtyRegions(tmpDirtyComponents);
 824     }
 825 
 826     private void paintDirtyRegions(
 827         final Map<Component,Rectangle> tmpDirtyComponents)
 828     {
 829         if (tmpDirtyComponents.isEmpty()) {
 830             return;
 831         }
 832 
 833         final java.util.List<Component> roots =
 834             new ArrayList<Component>(tmpDirtyComponents.size());
 835         for (Component dirty : tmpDirtyComponents.keySet()) {
 836             collectDirtyComponents(tmpDirtyComponents, dirty, roots);
 837         }
 838 
 839         final AtomicInteger count = new AtomicInteger(roots.size());
 840         painting = true;
 841         try {
 842             for (int j=0 ; j < count.get(); j++) {
 843                 final int i = j;
 844                 final Component dirtyComponent = roots.get(j);
 845                 AccessControlContext stack = AccessController.getContext();
 846                 AccessControlContext acc =
 847                     AWTAccessor.getComponentAccessor().getAccessControlContext(dirtyComponent);
 848                 javaSecurityAccess.doIntersectionPrivilege(new PrivilegedAction<Void>() {
 849                     public Void run() {
 850                         Rectangle rect = tmpDirtyComponents.get(dirtyComponent);
 851                         // Sometimes when RepaintManager is changed during the painting
 852                         // we may get null here, see #6995769 for details
 853                         if (rect == null) {
 854                             return null;
 855                         }
 856 
 857                         int localBoundsH = dirtyComponent.getHeight();
 858                         int localBoundsW = dirtyComponent.getWidth();
 859                         SwingUtilities.computeIntersection(0,
 860                                                            0,
 861                                                            localBoundsW,
 862                                                            localBoundsH,
 863                                                            rect);
 864                         if (dirtyComponent instanceof JComponent) {
 865                             ((JComponent)dirtyComponent).paintImmediately(
 866                                 rect.x,rect.y,rect.width, rect.height);
 867                         }
 868                         else if (dirtyComponent.isShowing()) {
 869                             Graphics g = JComponent.safelyGetGraphics(
 870                                     dirtyComponent, dirtyComponent);
 871                             // If the Graphics goes away, it means someone disposed of
 872                             // the window, don't do anything.
 873                             if (g != null) {
 874                                 g.setClip(rect.x, rect.y, rect.width, rect.height);
 875                                 try {
 876                                     dirtyComponent.paint(g);
 877                                 } finally {
 878                                     g.dispose();
 879                                 }
 880                             }
 881                         }
 882                         // If the repaintRoot has been set, service it now and
 883                         // remove any components that are children of repaintRoot.
 884                         if (repaintRoot != null) {
 885                             adjustRoots(repaintRoot, roots, i + 1);
 886                             count.set(roots.size());
 887                             paintManager.isRepaintingRoot = true;
 888                             repaintRoot.paintImmediately(0, 0, repaintRoot.getWidth(),
 889                                                          repaintRoot.getHeight());
 890                             paintManager.isRepaintingRoot = false;
 891                             // Only service repaintRoot once.
 892                             repaintRoot = null;
 893                         }
 894 
 895                         return null;
 896                     }
 897                 }, stack, acc);
 898             }
 899         } finally {
 900             painting = false;
 901         }
 902 
 903         updateWindows(tmpDirtyComponents);
 904 
 905         tmpDirtyComponents.clear();
 906     }
 907 
 908 
 909     /**
 910      * Removes any components from roots that are children of
 911      * root.
 912      */
 913     private void adjustRoots(JComponent root,
 914                              java.util.List<Component> roots, int index) {
 915         for (int i = roots.size() - 1; i >= index; i--) {
 916             Component c = roots.get(i);
 917             for(;;) {
 918                 if (c == root || c == null || !(c instanceof JComponent)) {
 919                     break;
 920                 }
 921                 c = c.getParent();
 922             }
 923             if (c == root) {
 924                 roots.remove(i);
 925             }
 926         }
 927     }
 928 
 929     Rectangle tmp = new Rectangle();
 930 
 931     void collectDirtyComponents(Map<Component,Rectangle> dirtyComponents,
 932                                 Component dirtyComponent,
 933                                 java.util.List<Component> roots) {
 934         int dx, dy, rootDx, rootDy;
 935         Component component, rootDirtyComponent,parent;
 936         Rectangle cBounds;
 937 
 938         // Find the highest parent which is dirty.  When we get out of this
 939         // rootDx and rootDy will contain the translation from the
 940         // rootDirtyComponent's coordinate system to the coordinates of the
 941         // original dirty component.  The tmp Rect is also used to compute the
 942         // visible portion of the dirtyRect.
 943 
 944         component = rootDirtyComponent = dirtyComponent;
 945 
 946         int x = dirtyComponent.getX();
 947         int y = dirtyComponent.getY();
 948         int w = dirtyComponent.getWidth();
 949         int h = dirtyComponent.getHeight();
 950 
 951         dx = rootDx = 0;
 952         dy = rootDy = 0;
 953         tmp.setBounds(dirtyComponents.get(dirtyComponent));
 954 
 955         // System.out.println("Collect dirty component for bound " + tmp +
 956         //                                   "component bounds is " + cBounds);;
 957         SwingUtilities.computeIntersection(0,0,w,h,tmp);
 958 
 959         if (tmp.isEmpty()) {
 960             // System.out.println("Empty 1");
 961             return;
 962         }
 963 
 964         for(;;) {
 965             if(!(component instanceof JComponent))
 966                 break;
 967 
 968             parent = component.getParent();
 969             if(parent == null)
 970                 break;
 971 
 972             component = parent;
 973 
 974             dx += x;
 975             dy += y;
 976             tmp.setLocation(tmp.x + x, tmp.y + y);
 977 
 978             x = component.getX();
 979             y = component.getY();
 980             w = component.getWidth();
 981             h = component.getHeight();
 982             tmp = SwingUtilities.computeIntersection(0,0,w,h,tmp);
 983 
 984             if (tmp.isEmpty()) {
 985                 // System.out.println("Empty 2");
 986                 return;
 987             }
 988 
 989             if (dirtyComponents.get(component) != null) {
 990                 rootDirtyComponent = component;
 991                 rootDx = dx;
 992                 rootDy = dy;
 993             }
 994         }
 995 
 996         if (dirtyComponent != rootDirtyComponent) {
 997             Rectangle r;
 998             tmp.setLocation(tmp.x + rootDx - dx,
 999                             tmp.y + rootDy - dy);
1000             r = dirtyComponents.get(rootDirtyComponent);
1001             SwingUtilities.computeUnion(tmp.x,tmp.y,tmp.width,tmp.height,r);
1002         }
1003 
1004         // If we haven't seen this root before, then we need to add it to the
1005         // list of root dirty Views.
1006 
1007         if (!roots.contains(rootDirtyComponent))
1008             roots.add(rootDirtyComponent);
1009     }
1010 
1011 
1012     /**
1013      * Returns a string that displays and identifies this
1014      * object's properties.
1015      *
1016      * @return a String representation of this object
1017      */
1018     public synchronized String toString() {
1019         StringBuilder sb = new StringBuilder();
1020         if(dirtyComponents != null)
1021             sb.append("" + dirtyComponents);
1022         return sb.toString();
1023     }
1024 
1025 
1026     /**
1027      * Return the offscreen buffer that should be used as a double buffer with
1028      * the component <code>c</code>.
1029      * By default there is a double buffer per RepaintManager.
1030      * The buffer might be smaller than <code>(proposedWidth,proposedHeight)</code>
1031      * This happens when the maximum double buffer size as been set for the receiving
1032      * repaint manager.
1033      *
1034      * @param c the component
1035      * @param proposedWidth the width of the buffer
1036      * @param proposedHeight the height of the buffer
1037      *
1038      * @return the image
1039      */
1040     public Image getOffscreenBuffer(Component c,int proposedWidth,int proposedHeight) {
1041         RepaintManager delegate = getDelegate(c);
1042         if (delegate != null) {
1043             return delegate.getOffscreenBuffer(c, proposedWidth, proposedHeight);
1044         }
1045         return _getOffscreenBuffer(c, proposedWidth, proposedHeight);
1046     }
1047 
1048     /**
1049      * Return a volatile offscreen buffer that should be used as a
1050      * double buffer with the specified component <code>c</code>.
1051      * The image returned will be an instance of VolatileImage, or null
1052      * if a VolatileImage object could not be instantiated.
1053      * This buffer might be smaller than <code>(proposedWidth,proposedHeight)</code>.
1054      * This happens when the maximum double buffer size has been set for this
1055      * repaint manager.
1056      *
1057      * @param c the component
1058      * @param proposedWidth the width of the buffer
1059      * @param proposedHeight the height of the buffer
1060      *
1061      * @return the volatile image
1062      * @see java.awt.image.VolatileImage
1063      * @since 1.4
1064      */
1065     public Image getVolatileOffscreenBuffer(Component c,
1066                                             int proposedWidth,int proposedHeight) {
1067         RepaintManager delegate = getDelegate(c);
1068         if (delegate != null) {
1069             return delegate.getVolatileOffscreenBuffer(c, proposedWidth,
1070                                                         proposedHeight);
1071         }
1072 
1073         // If the window is non-opaque, it's double-buffered at peer's level
1074         Window w = (c instanceof Window) ? (Window)c : SwingUtilities.getWindowAncestor(c);
1075         if (!w.isOpaque()) {
1076             Toolkit tk = Toolkit.getDefaultToolkit();
1077             if ((tk instanceof SunToolkit) && (((SunToolkit)tk).needUpdateWindow())) {
1078                 return null;
1079             }
1080         }
1081 
1082         GraphicsConfiguration config = c.getGraphicsConfiguration();
1083         if (config == null) {
1084             config = GraphicsEnvironment.getLocalGraphicsEnvironment().
1085                             getDefaultScreenDevice().getDefaultConfiguration();
1086         }
1087         Dimension maxSize = getDoubleBufferMaximumSize();
1088         int width = proposedWidth < 1 ? 1 :
1089             (proposedWidth > maxSize.width? maxSize.width : proposedWidth);
1090         int height = proposedHeight < 1 ? 1 :
1091             (proposedHeight > maxSize.height? maxSize.height : proposedHeight);
1092         VolatileImage image = volatileMap.get(config);
1093         if (image == null || image.getWidth() < width ||
1094                              image.getHeight() < height) {
1095             if (image != null) {
1096                 image.flush();
1097             }
1098             image = config.createCompatibleVolatileImage(width, height,
1099                                                          volatileBufferType);
1100             volatileMap.put(config, image);
1101         }
1102         return image;
1103     }
1104 
1105     private Image _getOffscreenBuffer(Component c, int proposedWidth, int proposedHeight) {
1106         Dimension maxSize = getDoubleBufferMaximumSize();
1107         DoubleBufferInfo doubleBuffer;
1108         int width, height;
1109 
1110         // If the window is non-opaque, it's double-buffered at peer's level
1111         Window w = (c instanceof Window) ? (Window)c : SwingUtilities.getWindowAncestor(c);
1112         if (!w.isOpaque()) {
1113             Toolkit tk = Toolkit.getDefaultToolkit();
1114             if ((tk instanceof SunToolkit) && (((SunToolkit)tk).needUpdateWindow())) {
1115                 return null;
1116             }
1117         }
1118 
1119         if (standardDoubleBuffer == null) {
1120             standardDoubleBuffer = new DoubleBufferInfo();
1121         }
1122         doubleBuffer = standardDoubleBuffer;
1123 
1124         width = proposedWidth < 1? 1 :
1125                   (proposedWidth > maxSize.width? maxSize.width : proposedWidth);
1126         height = proposedHeight < 1? 1 :
1127                   (proposedHeight > maxSize.height? maxSize.height : proposedHeight);
1128 
1129         if (doubleBuffer.needsReset || (doubleBuffer.image != null &&
1130                                         (doubleBuffer.size.width < width ||
1131                                          doubleBuffer.size.height < height))) {
1132             doubleBuffer.needsReset = false;
1133             if (doubleBuffer.image != null) {
1134                 doubleBuffer.image.flush();
1135                 doubleBuffer.image = null;
1136             }
1137             width = Math.max(doubleBuffer.size.width, width);
1138             height = Math.max(doubleBuffer.size.height, height);
1139         }
1140 
1141         Image result = doubleBuffer.image;
1142 
1143         if (doubleBuffer.image == null) {
1144             result = c.createImage(width , height);
1145             doubleBuffer.size = new Dimension(width, height);
1146             if (c instanceof JComponent) {
1147                 ((JComponent)c).setCreatedDoubleBuffer(true);
1148                 doubleBuffer.image = result;
1149             }
1150             // JComponent will inform us when it is no longer valid
1151             // (via removeNotify) we have no such hook to other components,
1152             // therefore we don't keep a ref to the Component
1153             // (indirectly through the Image) by stashing the image.
1154         }
1155         return result;
1156     }
1157 
1158 
1159     /**
1160      * Set the maximum double buffer size.
1161      *
1162      * @param d the dimension
1163      */
1164     public void setDoubleBufferMaximumSize(Dimension d) {
1165         doubleBufferMaxSize = d;
1166         if (doubleBufferMaxSize == null) {
1167             isCustomMaxBufferSizeSet = false;
1168             clearImages();
1169         } else {
1170             isCustomMaxBufferSizeSet = true;
1171             clearImages(d.width, d.height);
1172         }
1173     }
1174 
1175     private void clearImages() {
1176         clearImages(0, 0);
1177     }
1178 
1179     private void clearImages(int width, int height) {
1180         if (standardDoubleBuffer != null && standardDoubleBuffer.image != null) {
1181             if (standardDoubleBuffer.image.getWidth(null) > width ||
1182                 standardDoubleBuffer.image.getHeight(null) > height) {
1183                 standardDoubleBuffer.image.flush();
1184                 standardDoubleBuffer.image = null;
1185             }
1186         }
1187         // Clear out the VolatileImages
1188         Iterator<GraphicsConfiguration> gcs = volatileMap.keySet().iterator();
1189         while (gcs.hasNext()) {
1190             GraphicsConfiguration gc = gcs.next();
1191             VolatileImage image = volatileMap.get(gc);
1192             if (image.getWidth() > width || image.getHeight() > height) {
1193                 image.flush();
1194                 gcs.remove();
1195             }
1196         }
1197     }
1198 
1199     /**
1200      * Returns the maximum double buffer size.
1201      *
1202      * @return a Dimension object representing the maximum size
1203      */
1204     public Dimension getDoubleBufferMaximumSize() {
1205         if (doubleBufferMaxSize == null) {
1206             try {
1207                 Rectangle virtualBounds = new Rectangle();
1208                 GraphicsEnvironment ge = GraphicsEnvironment.
1209                                                  getLocalGraphicsEnvironment();
1210                 for (GraphicsDevice gd : ge.getScreenDevices()) {
1211                     GraphicsConfiguration gc = gd.getDefaultConfiguration();
1212                     virtualBounds = virtualBounds.union(gc.getBounds());
1213                 }
1214                 doubleBufferMaxSize = new Dimension(virtualBounds.width,
1215                                                     virtualBounds.height);
1216             } catch (HeadlessException e) {
1217                 doubleBufferMaxSize = new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
1218             }
1219         }
1220         return doubleBufferMaxSize;
1221     }
1222 
1223     /**
1224      * Enables or disables double buffering in this RepaintManager.
1225      * CAUTION: The default value for this property is set for optimal
1226      * paint performance on the given platform and it is not recommended
1227      * that programs modify this property directly.
1228      *
1229      * @param aFlag  true to activate double buffering
1230      * @see #isDoubleBufferingEnabled
1231      */
1232     public void setDoubleBufferingEnabled(boolean aFlag) {
1233         doubleBufferingEnabled = aFlag;
1234         PaintManager paintManager = getPaintManager();
1235         if (!aFlag && paintManager.getClass() != PaintManager.class) {
1236             setPaintManager(new PaintManager());
1237         }
1238     }
1239 
1240     /**
1241      * Returns true if this RepaintManager is double buffered.
1242      * The default value for this property may vary from platform
1243      * to platform.  On platforms where native double buffering
1244      * is supported in the AWT, the default value will be <code>false</code>
1245      * to avoid unnecessary buffering in Swing.
1246      * On platforms where native double buffering is not supported,
1247      * the default value will be <code>true</code>.
1248      *
1249      * @return true if this object is double buffered
1250      */
1251     public boolean isDoubleBufferingEnabled() {
1252         return doubleBufferingEnabled;
1253     }
1254 
1255     /**
1256      * This resets the double buffer. Actually, it marks the double buffer
1257      * as invalid, the double buffer will then be recreated on the next
1258      * invocation of getOffscreenBuffer.
1259      */
1260     void resetDoubleBuffer() {
1261         if (standardDoubleBuffer != null) {
1262             standardDoubleBuffer.needsReset = true;
1263         }
1264     }
1265 
1266     /**
1267      * This resets the volatile double buffer.
1268      */
1269     void resetVolatileDoubleBuffer(GraphicsConfiguration gc) {
1270         Image image = volatileMap.remove(gc);
1271         if (image != null) {
1272             image.flush();
1273         }
1274     }
1275 
1276     /**
1277      * Returns true if we should use the <code>Image</code> returned
1278      * from <code>getVolatileOffscreenBuffer</code> to do double buffering.
1279      */
1280     boolean useVolatileDoubleBuffer() {
1281         return volatileImageBufferEnabled;
1282     }
1283 
1284     /**
1285      * Returns true if the current thread is the thread painting.  This
1286      * will return false if no threads are painting.
1287      */
1288     private synchronized boolean isPaintingThread() {
1289         return (Thread.currentThread() == paintThread);
1290     }
1291     //
1292     // Paint methods.  You very, VERY rarely need to invoke these.
1293     // They are invoked directly from JComponent's painting code and
1294     // when painting happens outside the normal flow: DefaultDesktopManager
1295     // and JViewport.  If you end up needing these methods in other places be
1296     // careful that you don't get stuck in a paint loop.
1297     //
1298 
1299     /**
1300      * Paints a region of a component
1301      *
1302      * @param paintingComponent Component to paint
1303      * @param bufferComponent Component to obtain buffer for
1304      * @param g Graphics to paint to
1305      * @param x X-coordinate
1306      * @param y Y-coordinate
1307      * @param w Width
1308      * @param h Height
1309      */
1310     void paint(JComponent paintingComponent,
1311                JComponent bufferComponent, Graphics g,
1312                int x, int y, int w, int h) {
1313         PaintManager paintManager = getPaintManager();
1314         if (!isPaintingThread()) {
1315             // We're painting to two threads at once.  PaintManager deals
1316             // with this a bit better than BufferStrategyPaintManager, use
1317             // it to avoid possible exceptions/corruption.
1318             if (paintManager.getClass() != PaintManager.class) {
1319                 paintManager = new PaintManager();
1320                 paintManager.repaintManager = this;
1321             }
1322         }
1323         if (!paintManager.paint(paintingComponent, bufferComponent, g,
1324                                 x, y, w, h)) {
1325             g.setClip(x, y, w, h);
1326             paintingComponent.paintToOffscreen(g, x, y, w, h, x + w, y + h);
1327         }
1328     }
1329 
1330     /**
1331      * Does a copy area on the specified region.
1332      *
1333      * @param clip Whether or not the copyArea needs to be clipped to the
1334      *             Component's bounds.
1335      */
1336     void copyArea(JComponent c, Graphics g, int x, int y, int w, int h,
1337                   int deltaX, int deltaY, boolean clip) {
1338         getPaintManager().copyArea(c, g, x, y, w, h, deltaX, deltaY, clip);
1339     }
1340 
1341     private java.util.List<RepaintListener> repaintListeners = new ArrayList<>(1);
1342 
1343     private void addRepaintListener(RepaintListener l) {
1344         repaintListeners.add(l);
1345     }
1346 
1347     private void removeRepaintListener(RepaintListener l) {
1348         repaintListeners.remove(l);
1349     }
1350 
1351     /**
1352      * Notify the attached repaint listeners that an area of the {@code c} component
1353      * has been immediately repainted, that is without scheduling a repaint runnable,
1354      * due to performing a "blit" (via calling the {@code copyArea} method).
1355      *
1356      * @param c the component
1357      * @param x the x coordinate of the area
1358      * @param y the y coordinate of the area
1359      * @param w the width of the area
1360      * @param h the height of the area
1361      */
1362     void notifyRepaintPerformed(JComponent c, int x, int y, int w, int h) {
1363         for (RepaintListener l : repaintListeners) {
1364             l.repaintPerformed(c, x, y, w, h);
1365         }
1366     }
1367 
1368     /**
1369      * Invoked prior to any paint/copyArea method calls.  This will
1370      * be followed by an invocation of <code>endPaint</code>.
1371      * <b>WARNING</b>: Callers of this method need to wrap the call
1372      * in a <code>try/finally</code>, otherwise if an exception is thrown
1373      * during the course of painting the RepaintManager may
1374      * be left in a state in which the screen is not updated, eg:
1375      * <pre>
1376      * repaintManager.beginPaint();
1377      * try {
1378      *   repaintManager.paint(...);
1379      * } finally {
1380      *   repaintManager.endPaint();
1381      * }
1382      * </pre>
1383      */
1384     void beginPaint() {
1385         boolean multiThreadedPaint = false;
1386         int paintDepth;
1387         Thread currentThread = Thread.currentThread();
1388         synchronized(this) {
1389             paintDepth = this.paintDepth;
1390             if (paintThread == null || currentThread == paintThread) {
1391                 paintThread = currentThread;
1392                 this.paintDepth++;
1393             } else {
1394                 multiThreadedPaint = true;
1395             }
1396         }
1397         if (!multiThreadedPaint && paintDepth == 0) {
1398             getPaintManager().beginPaint();
1399         }
1400     }
1401 
1402     /**
1403      * Invoked after <code>beginPaint</code> has been invoked.
1404      */
1405     void endPaint() {
1406         if (isPaintingThread()) {
1407             PaintManager paintManager = null;
1408             synchronized(this) {
1409                 if (--paintDepth == 0) {
1410                     paintManager = getPaintManager();
1411                 }
1412             }
1413             if (paintManager != null) {
1414                 paintManager.endPaint();
1415                 synchronized(this) {
1416                     paintThread = null;
1417                 }
1418             }
1419         }
1420     }
1421 
1422     /**
1423      * If possible this will show a previously rendered portion of
1424      * a Component.  If successful, this will return true, otherwise false.
1425      * <p>
1426      * WARNING: This method is invoked from the native toolkit thread, be
1427      * very careful as to what methods this invokes!
1428      */
1429     boolean show(Container c, int x, int y, int w, int h) {
1430         return getPaintManager().show(c, x, y, w, h);
1431     }
1432 
1433     /**
1434      * Invoked when the doubleBuffered or useTrueDoubleBuffering
1435      * properties of a JRootPane change.  This may come in on any thread.
1436      */
1437     void doubleBufferingChanged(JRootPane rootPane) {
1438         getPaintManager().doubleBufferingChanged(rootPane);
1439     }
1440 
1441     /**
1442      * Sets the <code>PaintManager</code> that is used to handle all
1443      * double buffered painting.
1444      *
1445      * @param paintManager The PaintManager to use.  Passing in null indicates
1446      *        the fallback PaintManager should be used.
1447      */
1448     void setPaintManager(PaintManager paintManager) {
1449         if (paintManager == null) {
1450             paintManager = new PaintManager();
1451         }
1452         PaintManager oldPaintManager;
1453         synchronized(this) {
1454             oldPaintManager = this.paintManager;
1455             this.paintManager = paintManager;
1456             paintManager.repaintManager = this;
1457         }
1458         if (oldPaintManager != null) {
1459             oldPaintManager.dispose();
1460         }
1461     }
1462 
1463     private synchronized PaintManager getPaintManager() {
1464         if (paintManager == null) {
1465             PaintManager paintManager = null;
1466             if (doubleBufferingEnabled && !nativeDoubleBuffering) {
1467                 switch (bufferStrategyType) {
1468                 case BUFFER_STRATEGY_NOT_SPECIFIED:
1469                     Toolkit tk = Toolkit.getDefaultToolkit();
1470                     if (tk instanceof SunToolkit) {
1471                         SunToolkit stk = (SunToolkit) tk;
1472                         if (stk.useBufferPerWindow()) {
1473                             paintManager = new BufferStrategyPaintManager();
1474                         }
1475                     }
1476                     break;
1477                 case BUFFER_STRATEGY_SPECIFIED_ON:
1478                     paintManager = new BufferStrategyPaintManager();
1479                     break;
1480                 default:
1481                     break;
1482                 }
1483             }
1484             // null case handled in setPaintManager
1485             setPaintManager(paintManager);
1486         }
1487         return paintManager;
1488     }
1489 
1490     private void scheduleProcessingRunnable(AppContext context) {
1491         if (processingRunnable.markPending()) {
1492             Toolkit tk = Toolkit.getDefaultToolkit();
1493             if (tk instanceof SunToolkit) {
1494                 SunToolkit.getSystemEventQueueImplPP(context).
1495                   postEvent(new InvocationEvent(Toolkit.getDefaultToolkit(),
1496                                                 processingRunnable));
1497             } else {
1498                 Toolkit.getDefaultToolkit().getSystemEventQueue().
1499                       postEvent(new InvocationEvent(Toolkit.getDefaultToolkit(),
1500                                                     processingRunnable));
1501             }
1502         }
1503     }
1504 
1505 
1506     /**
1507      * PaintManager is used to handle all double buffered painting for
1508      * Swing.  Subclasses should call back into the JComponent method
1509      * <code>paintToOffscreen</code> to handle the actual painting.
1510      */
1511     static class PaintManager {
1512         /**
1513          * RepaintManager the PaintManager has been installed on.
1514          */
1515         protected RepaintManager repaintManager;
1516         boolean isRepaintingRoot;
1517 
1518         /**
1519          * Paints a region of a component
1520          *
1521          * @param paintingComponent Component to paint
1522          * @param bufferComponent Component to obtain buffer for
1523          * @param g Graphics to paint to
1524          * @param x X-coordinate
1525          * @param y Y-coordinate
1526          * @param w Width
1527          * @param h Height
1528          * @return true if painting was successful.
1529          */
1530         public boolean paint(JComponent paintingComponent,
1531                              JComponent bufferComponent, Graphics g,
1532                              int x, int y, int w, int h) {
1533             // First attempt to use VolatileImage buffer for performance.
1534             // If this fails (which should rarely occur), fallback to a
1535             // standard Image buffer.
1536             boolean paintCompleted = false;
1537             Image offscreen;
1538             int sw = w + 1;
1539             int sh = h + 1;
1540 
1541             if (repaintManager.useVolatileDoubleBuffer() &&
1542                 (offscreen = getValidImage(repaintManager.
1543                 getVolatileOffscreenBuffer(bufferComponent, sw, sh))) != null) {
1544                 VolatileImage vImage = (java.awt.image.VolatileImage)offscreen;
1545                 GraphicsConfiguration gc = bufferComponent.
1546                                             getGraphicsConfiguration();
1547                 for (int i = 0; !paintCompleted &&
1548                          i < RepaintManager.VOLATILE_LOOP_MAX; i++) {
1549                     if (vImage.validate(gc) ==
1550                                    VolatileImage.IMAGE_INCOMPATIBLE) {
1551                         repaintManager.resetVolatileDoubleBuffer(gc);
1552                         offscreen = repaintManager.getVolatileOffscreenBuffer(
1553                             bufferComponent, sw, sh);
1554                         vImage = (java.awt.image.VolatileImage)offscreen;
1555                     }
1556                     paintDoubleBuffered(paintingComponent, vImage, g, x, y,
1557                                         w, h);
1558                     paintCompleted = !vImage.contentsLost();
1559                 }
1560             }
1561             // VolatileImage painting loop failed, fallback to regular
1562             // offscreen buffer
1563             if (!paintCompleted && (offscreen = getValidImage(
1564                       repaintManager.getOffscreenBuffer(
1565                       bufferComponent, w, h))) != null) {
1566                 paintDoubleBuffered(paintingComponent, offscreen, g, x, y, w,
1567                                     h);
1568                 paintCompleted = true;
1569             }
1570             return paintCompleted;
1571         }
1572 
1573         /**
1574          * Does a copy area on the specified region.
1575          */
1576         public void copyArea(JComponent c, Graphics g, int x, int y, int w,
1577                              int h, int deltaX, int deltaY, boolean clip) {
1578             g.copyArea(x, y, w, h, deltaX, deltaY);
1579         }
1580 
1581         /**
1582          * Invoked prior to any calls to paint or copyArea.
1583          */
1584         public void beginPaint() {
1585         }
1586 
1587         /**
1588          * Invoked to indicate painting has been completed.
1589          */
1590         public void endPaint() {
1591         }
1592 
1593         /**
1594          * Shows a region of a previously rendered component.  This
1595          * will return true if successful, false otherwise.  The default
1596          * implementation returns false.
1597          */
1598         public boolean show(Container c, int x, int y, int w, int h) {
1599             return false;
1600         }
1601 
1602         /**
1603          * Invoked when the doubleBuffered or useTrueDoubleBuffering
1604          * properties of a JRootPane change.  This may come in on any thread.
1605          */
1606         public void doubleBufferingChanged(JRootPane rootPane) {
1607         }
1608 
1609         /**
1610          * Paints a portion of a component to an offscreen buffer.
1611          */
1612         protected void paintDoubleBuffered(JComponent c, Image image,
1613                 Graphics g, int clipX, int clipY,
1614                 int clipW, int clipH) {
1615             if (image instanceof VolatileImage && isPixelsCopying(c, g)) {
1616                 paintDoubleBufferedFPScales(c, image, g, clipX, clipY, clipW, clipH);
1617             } else {
1618                 paintDoubleBufferedImpl(c, image, g, clipX, clipY, clipW, clipH);
1619             }
1620         }
1621 
1622         private void paintDoubleBufferedImpl(JComponent c, Image image,
1623                                              Graphics g, int clipX, int clipY,
1624                                              int clipW, int clipH) {
1625             Graphics osg = image.getGraphics();
1626             int bw = Math.min(clipW, image.getWidth(null));
1627             int bh = Math.min(clipH, image.getHeight(null));
1628             int x,y,maxx,maxy;
1629 
1630             try {
1631                 for(x = clipX, maxx = clipX+clipW; x < maxx ;  x += bw ) {
1632                     for(y=clipY, maxy = clipY + clipH; y < maxy ; y += bh) {
1633                         osg.translate(-x, -y);
1634                         osg.setClip(x,y,bw,bh);
1635                         if (volatileBufferType != Transparency.OPAQUE
1636                                 && osg instanceof Graphics2D) {
1637                             final Graphics2D g2d = (Graphics2D) osg;
1638                             final Color oldBg = g2d.getBackground();
1639                             g2d.setBackground(c.getBackground());
1640                             g2d.clearRect(x, y, bw, bh);
1641                             g2d.setBackground(oldBg);
1642                         }
1643                         c.paintToOffscreen(osg, x, y, bw, bh, maxx, maxy);
1644                         g.setClip(x, y, bw, bh);
1645                         if (volatileBufferType != Transparency.OPAQUE
1646                                 && g instanceof Graphics2D) {
1647                             final Graphics2D g2d = (Graphics2D) g;
1648                             final Composite oldComposite = g2d.getComposite();
1649                             g2d.setComposite(AlphaComposite.Src);
1650                             g2d.drawImage(image, x, y, c);
1651                             g2d.setComposite(oldComposite);
1652                         } else {
1653                             g.drawImage(image, x, y, c);
1654                         }
1655                         osg.translate(x, y);
1656                     }
1657                 }
1658             } finally {
1659                 osg.dispose();
1660             }
1661         }
1662 
1663         private void paintDoubleBufferedFPScales(JComponent c, Image image,
1664                                                  Graphics g, int clipX, int clipY,
1665                                                  int clipW, int clipH) {
1666             Graphics osg = image.getGraphics();
1667             Graphics2D g2d = (Graphics2D) g;
1668             Graphics2D osg2d = (Graphics2D) osg;
1669 
1670             AffineTransform identity = new AffineTransform();
1671             int bw = Math.min(clipW, image.getWidth(null));
1672             int bh = Math.min(clipH, image.getHeight(null));
1673             int x, y, maxx, maxy;
1674 
1675             AffineTransform tx = g2d.getTransform();
1676             double scaleX = tx.getScaleX();
1677             double scaleY = tx.getScaleY();
1678             double trX = tx.getTranslateX();
1679             double trY = tx.getTranslateY();
1680 
1681             boolean translucent = volatileBufferType != Transparency.OPAQUE;
1682             Composite oldComposite = g2d.getComposite();
1683 
1684             try {
1685                 for (x = clipX, maxx = clipX + clipW; x < maxx; x += bw) {
1686                     for (y = clipY, maxy = clipY + clipH; y < maxy; y += bh) {
1687 
1688                         // draw x, y, bw, bh
1689                         int pixelx1 = Region.clipRound(x * scaleX + trX);
1690                         int pixely1 = Region.clipRound(y * scaleY + trY);
1691                         int pixelx2 = Region.clipRound((x + bw) * scaleX + trX);
1692                         int pixely2 = Region.clipRound((y + bh) * scaleY + trY);
1693                         int pixelw = pixelx2 - pixelx1;
1694                         int pixelh = pixely2 - pixely1;
1695 
1696                         osg2d.setTransform(identity);
1697                         if (translucent) {
1698                             final Color oldBg = g2d.getBackground();
1699                             g2d.setBackground(c.getBackground());
1700                             g2d.clearRect(pixelx1, pixely1, pixelw, pixelh);
1701                             g2d.setBackground(oldBg);
1702                         }
1703 
1704                         osg2d.setClip(0, 0, pixelw, pixelh);
1705                         osg2d.translate(trX - pixelx1, trY - pixely1);
1706                         osg2d.scale(scaleX, scaleY);
1707                         c.paintToOffscreen(osg, x, y, bw, bh, maxx, maxy);
1708 
1709                         g2d.setTransform(identity);
1710                         g2d.setClip(pixelx1, pixely1, pixelw, pixelh);
1711                         AffineTransform stx = new AffineTransform();
1712                         stx.translate(pixelx1, pixely1);
1713                         stx.scale(scaleX, scaleY);
1714                         g2d.setTransform(stx);
1715 
1716                         if (translucent) {
1717                             g2d.setComposite(AlphaComposite.Src);
1718                         }
1719 
1720                         g2d.drawImage(image, 0, 0, c);
1721 
1722                         if (translucent) {
1723                             g2d.setComposite(oldComposite);
1724                         }
1725                         g2d.setTransform(tx);
1726                     }
1727                 }
1728             } finally {
1729                 osg.dispose();
1730             }
1731         }
1732 
1733         /**
1734          * If <code>image</code> is non-null with a positive size it
1735          * is returned, otherwise null is returned.
1736          */
1737         private Image getValidImage(Image image) {
1738             if (image != null && image.getWidth(null) > 0 &&
1739                                  image.getHeight(null) > 0) {
1740                 return image;
1741             }
1742             return null;
1743         }
1744 
1745         /**
1746          * Schedules a repaint for the specified component.  This differs
1747          * from <code>root.repaint</code> in that if the RepaintManager is
1748          * currently processing paint requests it'll process this request
1749          * with the current set of requests.
1750          */
1751         protected void repaintRoot(JComponent root) {
1752             assert (repaintManager.repaintRoot == null);
1753             if (repaintManager.painting) {
1754                 repaintManager.repaintRoot = root;
1755             }
1756             else {
1757                 root.repaint();
1758             }
1759         }
1760 
1761         /**
1762          * Returns true if the component being painted is the root component
1763          * that was previously passed to <code>repaintRoot</code>.
1764          */
1765         protected boolean isRepaintingRoot() {
1766             return isRepaintingRoot;
1767         }
1768 
1769         /**
1770          * Cleans up any state.  After invoked the PaintManager will no
1771          * longer be used anymore.
1772          */
1773         protected void dispose() {
1774         }
1775 
1776         private boolean isPixelsCopying(JComponent c, Graphics g) {
1777 
1778             AffineTransform tx = getTransform(g);
1779             GraphicsConfiguration gc = c.getGraphicsConfiguration();
1780 
1781             if (tx == null || gc == null
1782                     || !SwingUtilities2.isFloatingPointScale(tx)) {
1783                 return false;
1784             }
1785 
1786             AffineTransform gcTx = gc.getDefaultTransform();
1787 
1788             return gcTx.getScaleX() == tx.getScaleX()
1789                     && gcTx.getScaleY() == tx.getScaleY();
1790         }
1791 
1792         private static AffineTransform getTransform(Graphics g) {
1793             if (g instanceof SunGraphics2D) {
1794                 return ((SunGraphics2D) g).transform;
1795             } else if (g instanceof Graphics2D) {
1796                 return ((Graphics2D) g).getTransform();
1797             }
1798             return null;
1799         }
1800     }
1801 
1802     private class DoubleBufferInfo {
1803         public Image image;
1804         public Dimension size;
1805         public boolean needsReset = false;
1806     }
1807 
1808 
1809     /**
1810      * Listener installed to detect display changes. When display changes,
1811      * schedules a callback to notify all RepaintManagers of the display
1812      * changes. Only one DisplayChangedHandler is ever installed. The
1813      * singleton instance will schedule notification for all AppContexts.
1814      */
1815     private static final class DisplayChangedHandler implements
1816                                              DisplayChangedListener {
1817         // Empty non private constructor was added because access to this
1818         // class shouldn't be generated by the compiler using synthetic
1819         // accessor method
1820         DisplayChangedHandler() {
1821         }
1822 
1823         public void displayChanged() {
1824             scheduleDisplayChanges();
1825         }
1826 
1827         public void paletteChanged() {
1828         }
1829 
1830         private static void scheduleDisplayChanges() {
1831             // To avoid threading problems, we notify each RepaintManager
1832             // on the thread it was created on.
1833             for (AppContext context : AppContext.getAppContexts()) {
1834                 synchronized(context) {
1835                     if (!context.isDisposed()) {
1836                         EventQueue eventQueue = (EventQueue)context.get(
1837                             AppContext.EVENT_QUEUE_KEY);
1838                         if (eventQueue != null) {
1839                             eventQueue.postEvent(new InvocationEvent(
1840                                 Toolkit.getDefaultToolkit(),
1841                                 new DisplayChangedRunnable()));
1842                         }
1843                     }
1844                 }
1845             }
1846         }
1847     }
1848 
1849 
1850     private static final class DisplayChangedRunnable implements Runnable {
1851         public void run() {
1852             RepaintManager.currentManager((JComponent)null).displayChanged();
1853         }
1854     }
1855 
1856 
1857     /**
1858      * Runnable used to process all repaint/revalidate requests.
1859      */
1860     private final class ProcessingRunnable implements Runnable {
1861         // If true, we're wainting on the EventQueue.
1862         private boolean pending;
1863 
1864         /**
1865          * Marks this processing runnable as pending. If this was not
1866          * already marked as pending, true is returned.
1867          */
1868         public synchronized boolean markPending() {
1869             if (!pending) {
1870                 pending = true;
1871                 return true;
1872             }
1873             return false;
1874         }
1875 
1876         public void run() {
1877             synchronized (this) {
1878                 pending = false;
1879             }
1880             // First pass, flush any heavy paint events into real paint
1881             // events.  If there are pending heavy weight requests this will
1882             // result in q'ing this request up one more time.  As
1883             // long as no other requests come in between now and the time
1884             // the second one is processed nothing will happen.  This is not
1885             // ideal, but the logic needed to suppress the second request is
1886             // more headache than it's worth.
1887             scheduleHeavyWeightPaints();
1888             // Do the actual validation and painting.
1889             validateInvalidComponents();
1890             prePaintDirtyRegions();
1891         }
1892     }
1893     private RepaintManager getDelegate(Component c) {
1894         RepaintManager delegate = SwingUtilities3.getDelegateRepaintManager(c);
1895         if (this == delegate) {
1896             delegate = null;
1897         }
1898         return delegate;
1899     }
1900 }