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