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