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