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