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