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