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