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