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