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