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