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