1 /* RepaintManager.java -- 2 Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. 3 4 This file is part of GNU Classpath. 5 6 GNU Classpath is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 GNU Classpath is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with GNU Classpath; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19 02110-1301 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 38 39 package javax.swing; 40 41 import gnu.classpath.SystemProperties; 42 import gnu.java.awt.LowPriorityEvent; 43 44 import java.applet.Applet; 45 import java.awt.Component; 46 import java.awt.Dimension; 47 import java.awt.EventQueue; 48 import java.awt.Graphics; 49 import java.awt.Image; 50 import java.awt.Rectangle; 51 import java.awt.Toolkit; 52 import java.awt.Window; 53 import java.awt.event.InvocationEvent; 54 import java.awt.image.VolatileImage; 55 import java.util.ArrayList; 56 import java.util.HashMap; 57 import java.util.HashSet; 58 import java.util.Iterator; 59 import java.util.Set; 60 import java.util.WeakHashMap; 61 62 /** 63 * <p>The repaint manager holds a set of dirty regions, invalid components, 64 * and a double buffer surface. The dirty regions and invalid components 65 * are used to coalesce multiple revalidate() and repaint() calls in the 66 * component tree into larger groups to be refreshed "all at once"; the 67 * double buffer surface is used by root components to paint 68 * themselves.</p> 69 * 70 * <p>See <a 71 * href="http://java.sun.com/products/jfc/tsc/articles/painting/index.html">this 72 * document</a> for more details.</p> 73 * document</a> for more details.</p> 74 * 75 * @author Roman Kennke (kennke@aicas.com) 76 * @author Graydon Hoare (graydon@redhat.com) 77 * @author Audrius Meskauskas (audriusa@bioinformatics.org) 78 */ 79 public class RepaintManager 80 { 81 /** 82 * An InvocationEvent subclass that implements LowPriorityEvent. This is used 83 * to defer the execution of RepaintManager requests as long as possible on 84 * the event queue. This way we make sure that all available input is 85 * processed before getting active with the RepaintManager. This allows 86 * for better optimization (more validate and repaint requests can be 87 * coalesced) and thus has a positive effect on performance for GUI 88 * applications under heavy load. 89 */ 90 private static class RepaintWorkerEvent 91 extends InvocationEvent 92 implements LowPriorityEvent 93 { 94 95 /** 96 * Creates a new RepaintManager event. 97 * 98 * @param source the source 99 * @param runnable the runnable to execute 100 */ RepaintWorkerEvent(Object source, Runnable runnable, Object notifier, boolean catchEx)101 public RepaintWorkerEvent(Object source, Runnable runnable, 102 Object notifier, boolean catchEx) 103 { 104 super(source, runnable, notifier, catchEx); 105 } 106 107 /** 108 * An application that I met implements its own event dispatching and 109 * calls dispatch() via reflection, and only checks declared methods, 110 * that is, it expects this method to be in the event's class, not 111 * in a superclass. So I put this in here... sigh. 112 */ dispatch()113 public void dispatch() 114 { 115 super.dispatch(); 116 } 117 } 118 119 /** 120 * The current repaint managers, indexed by their ThreadGroups. 121 */ 122 static WeakHashMap currentRepaintManagers; 123 124 /** 125 * A rectangle object to be reused in damaged regions calculation. 126 */ 127 private static Rectangle rectCache = new Rectangle(); 128 129 /** 130 * <p>A helper class which is placed into the system event queue at 131 * various times in order to facilitate repainting and layout. There is 132 * typically only one of these objects active at any time. When the 133 * {@link RepaintManager} is told to queue a repaint, it checks to see if 134 * a {@link RepaintWorker} is "live" in the system event queue, and if 135 * not it inserts one using {@link SwingUtilities#invokeLater}.</p> 136 * 137 * <p>When the {@link RepaintWorker} comes to the head of the system 138 * event queue, its {@link RepaintWorker#run} method is executed by the 139 * swing paint thread, which revalidates all invalid components and 140 * repaints any damage in the swing scene.</p> 141 */ 142 private class RepaintWorker 143 implements Runnable 144 { 145 146 boolean live; 147 RepaintWorker()148 public RepaintWorker() 149 { 150 live = false; 151 } 152 setLive(boolean b)153 public synchronized void setLive(boolean b) 154 { 155 live = b; 156 } 157 isLive()158 public synchronized boolean isLive() 159 { 160 return live; 161 } 162 run()163 public void run() 164 { 165 try 166 { 167 ThreadGroup threadGroup = Thread.currentThread().getThreadGroup(); 168 RepaintManager rm = 169 (RepaintManager) currentRepaintManagers.get(threadGroup); 170 rm.validateInvalidComponents(); 171 rm.paintDirtyRegions(); 172 } 173 finally 174 { 175 setLive(false); 176 } 177 } 178 179 } 180 181 /** 182 * A table storing the dirty regions of components. The keys of this 183 * table are components, the values are rectangles. Each component maps 184 * to exactly one rectangle. When more regions are marked as dirty on a 185 * component, they are union'ed with the existing rectangle. 186 * 187 * This is package private to avoid a synthetic accessor method in inner 188 * class. 189 * 190 * @see #addDirtyRegion 191 * @see #getDirtyRegion 192 * @see #isCompletelyDirty 193 * @see #markCompletelyClean 194 * @see #markCompletelyDirty 195 */ 196 private HashMap dirtyComponents; 197 198 /** 199 * The dirtyComponents which is used in paintDiryRegions to avoid unnecessary 200 * locking. 201 */ 202 private HashMap dirtyComponentsWork; 203 204 /** 205 * A single, shared instance of the helper class. Any methods which mark 206 * components as invalid or dirty eventually activate this instance. It 207 * is added to the event queue if it is not already active, otherwise 208 * reused. 209 * 210 * @see #addDirtyRegion 211 * @see #addInvalidComponent 212 */ 213 private RepaintWorker repaintWorker; 214 215 /** 216 * The set of components which need revalidation, in the "layout" sense. 217 * There is no additional information about "what kind of layout" they 218 * need (as there is with dirty regions), so it is just a vector rather 219 * than a table. 220 * 221 * @see #addInvalidComponent 222 * @see #removeInvalidComponent 223 * @see #validateInvalidComponents 224 */ 225 private ArrayList invalidComponents; 226 227 /** 228 * Whether or not double buffering is enabled on this repaint 229 * manager. This is merely a hint to clients; the RepaintManager will 230 * always return an offscreen buffer when one is requested. 231 * 232 * @see #isDoubleBufferingEnabled 233 * @see #setDoubleBufferingEnabled 234 */ 235 private boolean doubleBufferingEnabled; 236 237 /** 238 * The offscreen buffers. This map holds one offscreen buffer per 239 * Window/Applet and releases them as soon as the Window/Applet gets garbage 240 * collected. 241 */ 242 private WeakHashMap offscreenBuffers; 243 244 /** 245 * The maximum width and height to allocate as a double buffer. Requests 246 * beyond this size are ignored. 247 * 248 * @see #paintDirtyRegions 249 * @see #getDoubleBufferMaximumSize 250 * @see #setDoubleBufferMaximumSize 251 */ 252 private Dimension doubleBufferMaximumSize; 253 254 255 /** 256 * Create a new RepaintManager object. 257 */ RepaintManager()258 public RepaintManager() 259 { 260 dirtyComponents = new HashMap(); 261 dirtyComponentsWork = new HashMap(); 262 invalidComponents = new ArrayList(); 263 repaintWorker = new RepaintWorker(); 264 doubleBufferMaximumSize = new Dimension(2000,2000); 265 doubleBufferingEnabled = 266 SystemProperties.getProperty("gnu.swing.doublebuffering", "true") 267 .equals("true"); 268 offscreenBuffers = new WeakHashMap(); 269 } 270 271 /** 272 * Returns the <code>RepaintManager</code> for the current thread's 273 * thread group. The default implementation ignores the 274 * <code>component</code> parameter and returns the same repaint manager 275 * for all components. 276 * 277 * @param component a component to look up the manager of 278 * 279 * @return the current repaint manager for the calling thread's thread group 280 * and the specified component 281 * 282 * @see #setCurrentManager 283 */ currentManager(Component component)284 public static RepaintManager currentManager(Component component) 285 { 286 if (currentRepaintManagers == null) 287 currentRepaintManagers = new WeakHashMap(); 288 ThreadGroup threadGroup = Thread.currentThread().getThreadGroup(); 289 RepaintManager currentManager = 290 (RepaintManager) currentRepaintManagers.get(threadGroup); 291 if (currentManager == null) 292 { 293 currentManager = new RepaintManager(); 294 currentRepaintManagers.put(threadGroup, currentManager); 295 } 296 return currentManager; 297 } 298 299 /** 300 * Returns the <code>RepaintManager</code> for the current thread's 301 * thread group. The default implementation ignores the 302 * <code>component</code> parameter and returns the same repaint manager 303 * for all components. 304 * 305 * This method is only here for backwards compatibility with older versions 306 * of Swing and simply forwards to {@link #currentManager(Component)}. 307 * 308 * @param component a component to look up the manager of 309 * 310 * @return the current repaint manager for the calling thread's thread group 311 * and the specified component 312 * 313 * @see #setCurrentManager 314 */ currentManager(JComponent component)315 public static RepaintManager currentManager(JComponent component) 316 { 317 return currentManager((Component)component); 318 } 319 320 /** 321 * Sets the repaint manager for the calling thread's thread group. 322 * 323 * @param manager the repaint manager to set for the current thread's thread 324 * group 325 * 326 * @see #currentManager(Component) 327 */ setCurrentManager(RepaintManager manager)328 public static void setCurrentManager(RepaintManager manager) 329 { 330 if (currentRepaintManagers == null) 331 currentRepaintManagers = new WeakHashMap(); 332 333 ThreadGroup threadGroup = Thread.currentThread().getThreadGroup(); 334 currentRepaintManagers.put(threadGroup, manager); 335 } 336 337 /** 338 * Add a component to the {@link #invalidComponents} vector. If the 339 * {@link #repaintWorker} class is not active, insert it in the system 340 * event queue. 341 * 342 * @param component The component to add 343 * 344 * @see #removeInvalidComponent 345 */ addInvalidComponent(JComponent component)346 public void addInvalidComponent(JComponent component) 347 { 348 Component validateRoot = null; 349 Component c = component; 350 while (c != null) 351 { 352 // Special cases we don't bother validating are when the invalidated 353 // component (or any of it's ancestors) is inside a CellRendererPane 354 // or if it doesn't have a peer yet (== not displayable). 355 if (c instanceof CellRendererPane || ! c.isDisplayable()) 356 return; 357 if (c instanceof JComponent && ((JComponent) c).isValidateRoot()) 358 { 359 validateRoot = c; 360 break; 361 } 362 363 c = c.getParent(); 364 } 365 366 // If we didn't find a validate root, then we don't validate. 367 if (validateRoot == null) 368 return; 369 370 // Make sure the validate root and all of it's ancestors are visible. 371 c = validateRoot; 372 while (c != null) 373 { 374 if (! c.isVisible() || ! c.isDisplayable()) 375 return; 376 c = c.getParent(); 377 } 378 379 if (invalidComponents.contains(validateRoot)) 380 return; 381 382 //synchronized (invalidComponents) 383 // { 384 invalidComponents.add(validateRoot); 385 // } 386 387 if (! repaintWorker.isLive()) 388 { 389 repaintWorker.setLive(true); 390 invokeLater(repaintWorker); 391 } 392 } 393 394 /** 395 * Remove a component from the {@link #invalidComponents} vector. 396 * 397 * @param component The component to remove 398 * 399 * @see #addInvalidComponent 400 */ removeInvalidComponent(JComponent component)401 public void removeInvalidComponent(JComponent component) 402 { 403 synchronized (invalidComponents) 404 { 405 invalidComponents.remove(component); 406 } 407 } 408 409 /** 410 * Add a region to the set of dirty regions for a specified component. 411 * This involves union'ing the new region with any existing dirty region 412 * associated with the component. If the {@link #repaintWorker} class 413 * is not active, insert it in the system event queue. 414 * 415 * @param component The component to add a dirty region for 416 * @param x The left x coordinate of the new dirty region 417 * @param y The top y coordinate of the new dirty region 418 * @param w The width of the new dirty region 419 * @param h The height of the new dirty region 420 * 421 * @see #addDirtyRegion 422 * @see #getDirtyRegion 423 * @see #isCompletelyDirty 424 * @see #markCompletelyClean 425 * @see #markCompletelyDirty 426 */ addDirtyRegion(JComponent component, int x, int y, int w, int h)427 public void addDirtyRegion(JComponent component, int x, int y, 428 int w, int h) 429 { 430 if (w <= 0 || h <= 0 || !component.isShowing()) 431 return; 432 component.computeVisibleRect(rectCache); 433 SwingUtilities.computeIntersection(x, y, w, h, rectCache); 434 435 if (! rectCache.isEmpty()) 436 { 437 synchronized (dirtyComponents) 438 { 439 Rectangle dirtyRect = (Rectangle)dirtyComponents.get(component); 440 if (dirtyRect != null) 441 { 442 SwingUtilities.computeUnion(rectCache.x, rectCache.y, 443 rectCache.width, rectCache.height, 444 dirtyRect); 445 } 446 else 447 { 448 dirtyComponents.put(component, rectCache.getBounds()); 449 } 450 } 451 452 if (! repaintWorker.isLive()) 453 { 454 repaintWorker.setLive(true); 455 invokeLater(repaintWorker); 456 } 457 } 458 } 459 460 /** 461 * Get the dirty region associated with a component, or <code>null</code> 462 * if the component has no dirty region. 463 * 464 * @param component The component to get the dirty region of 465 * 466 * @return The dirty region of the component 467 * 468 * @see #dirtyComponents 469 * @see #addDirtyRegion 470 * @see #isCompletelyDirty 471 * @see #markCompletelyClean 472 * @see #markCompletelyDirty 473 */ getDirtyRegion(JComponent component)474 public Rectangle getDirtyRegion(JComponent component) 475 { 476 Rectangle dirty = (Rectangle) dirtyComponents.get(component); 477 if (dirty == null) 478 dirty = new Rectangle(); 479 return dirty; 480 } 481 482 /** 483 * Mark a component as dirty over its entire bounds. 484 * 485 * @param component The component to mark as dirty 486 * 487 * @see #dirtyComponents 488 * @see #addDirtyRegion 489 * @see #getDirtyRegion 490 * @see #isCompletelyDirty 491 * @see #markCompletelyClean 492 */ markCompletelyDirty(JComponent component)493 public void markCompletelyDirty(JComponent component) 494 { 495 addDirtyRegion(component, 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE); 496 } 497 498 /** 499 * Remove all dirty regions for a specified component 500 * 501 * @param component The component to mark as clean 502 * 503 * @see #dirtyComponents 504 * @see #addDirtyRegion 505 * @see #getDirtyRegion 506 * @see #isCompletelyDirty 507 * @see #markCompletelyDirty 508 */ markCompletelyClean(JComponent component)509 public void markCompletelyClean(JComponent component) 510 { 511 synchronized (dirtyComponents) 512 { 513 dirtyComponents.remove(component); 514 } 515 } 516 517 /** 518 * Return <code>true</code> if the specified component is completely 519 * contained within its dirty region, otherwise <code>false</code> 520 * 521 * @param component The component to check for complete dirtyness 522 * 523 * @return Whether the component is completely dirty 524 * 525 * @see #dirtyComponents 526 * @see #addDirtyRegion 527 * @see #getDirtyRegion 528 * @see #isCompletelyDirty 529 * @see #markCompletelyClean 530 */ isCompletelyDirty(JComponent component)531 public boolean isCompletelyDirty(JComponent component) 532 { 533 boolean dirty = false; 534 Rectangle r = getDirtyRegion(component); 535 if(r.width == Integer.MAX_VALUE && r.height == Integer.MAX_VALUE) 536 dirty = true; 537 return dirty; 538 } 539 540 /** 541 * Validate all components which have been marked invalid in the {@link 542 * #invalidComponents} vector. 543 */ validateInvalidComponents()544 public void validateInvalidComponents() 545 { 546 // We don't use an iterator here because that would fail when there are 547 // components invalidated during the validation of others, which happens 548 // quite frequently. Instead we synchronize the access a little more. 549 while (invalidComponents.size() > 0) 550 { 551 Component comp; 552 synchronized (invalidComponents) 553 { 554 comp = (Component) invalidComponents.remove(0); 555 } 556 // Validate the validate component. 557 if (! (comp.isVisible() && comp.isShowing())) 558 continue; 559 comp.validate(); 560 } 561 } 562 563 /** 564 * Repaint all regions of all components which have been marked dirty in the 565 * {@link #dirtyComponents} table. 566 */ paintDirtyRegions()567 public void paintDirtyRegions() 568 { 569 // Short circuit if there is nothing to paint. 570 if (dirtyComponents.size() == 0) 571 return; 572 573 // Swap dirtyRegions with dirtyRegionsWork to avoid locking. 574 synchronized (dirtyComponents) 575 { 576 HashMap swap = dirtyComponents; 577 dirtyComponents = dirtyComponentsWork; 578 dirtyComponentsWork = swap; 579 } 580 581 // Compile a set of repaint roots. 582 HashSet repaintRoots = new HashSet(); 583 Set components = dirtyComponentsWork.keySet(); 584 for (Iterator i = components.iterator(); i.hasNext();) 585 { 586 JComponent dirty = (JComponent) i.next(); 587 compileRepaintRoots(dirtyComponentsWork, dirty, repaintRoots); 588 } 589 590 for (Iterator i = repaintRoots.iterator(); i.hasNext();) 591 { 592 JComponent comp = (JComponent) i.next(); 593 Rectangle damaged = (Rectangle) dirtyComponentsWork.remove(comp); 594 if (damaged == null || damaged.isEmpty()) 595 continue; 596 comp.paintImmediately(damaged); 597 } 598 dirtyComponentsWork.clear(); 599 } 600 601 /** 602 * Compiles a list of components that really get repainted. This is called 603 * once for each component in the dirtyRegions HashMap, each time with 604 * another <code>dirty</code> parameter. This searches up the component 605 * hierarchy of <code>dirty</code> to find the highest parent that is also 606 * marked dirty and merges the dirty regions. 607 * 608 * @param dirtyRegions the dirty regions 609 * @param dirty the component for which to find the repaint root 610 * @param roots the list to which new repaint roots get appended 611 */ compileRepaintRoots(HashMap dirtyRegions, JComponent dirty, HashSet roots)612 private void compileRepaintRoots(HashMap dirtyRegions, JComponent dirty, 613 HashSet roots) 614 { 615 Component current = dirty; 616 Component root = dirty; 617 618 // This will contain the dirty region in the root coordinate system, 619 // possibly clipped by ancestor's bounds. 620 Rectangle originalDirtyRect = (Rectangle) dirtyRegions.get(dirty); 621 rectCache.setBounds(originalDirtyRect); 622 623 // The bounds of the current component. 624 int x = dirty.getX(); 625 int y = dirty.getY(); 626 int w = dirty.getWidth(); 627 int h = dirty.getHeight(); 628 629 // Do nothing if dirty region is clipped away by the component's bounds. 630 rectCache = SwingUtilities.computeIntersection(0, 0, w, h, rectCache); 631 if (rectCache.isEmpty()) 632 return; 633 634 // The cumulated offsets. 635 int dx = 0; 636 int dy = 0; 637 // The actual offset for the found root. 638 int rootDx = 0; 639 int rootDy = 0; 640 641 // Search the highest component that is also marked dirty. 642 Component parent; 643 while (true) 644 { 645 parent = current.getParent(); 646 if (parent == null || !(parent instanceof JComponent)) 647 break; 648 649 current = parent; 650 // Update the offset. 651 dx += x; 652 dy += y; 653 rectCache.x += x; 654 rectCache.y += y; 655 656 x = current.getX(); 657 y = current.getY(); 658 w = current.getWidth(); 659 h = current.getHeight(); 660 rectCache = SwingUtilities.computeIntersection(0, 0, w, h, rectCache); 661 662 // Don't paint if the dirty regions is clipped away by any of 663 // its ancestors. 664 if (rectCache.isEmpty()) 665 return; 666 667 // We can skip to the next up when this parent is not dirty. 668 if (dirtyRegions.containsKey(parent)) 669 { 670 root = current; 671 rootDx = dx; 672 rootDy = dy; 673 } 674 } 675 676 // Merge the rectangles of the root and the requested component if 677 // the are different. 678 if (root != dirty) 679 { 680 rectCache.x += rootDx - dx; 681 rectCache.y += rootDy - dy; 682 Rectangle dirtyRect = (Rectangle) dirtyRegions.get(root); 683 SwingUtilities.computeUnion(rectCache.x, rectCache.y, rectCache.width, 684 rectCache.height, dirtyRect); 685 } 686 687 // Adds the root to the roots set. 688 if (! roots.contains(root)) 689 roots.add(root); 690 } 691 692 /** 693 * Get an offscreen buffer for painting a component's image. This image 694 * may be smaller than the proposed dimensions, depending on the value of 695 * the {@link #doubleBufferMaximumSize} property. 696 * 697 * @param component The component to return an offscreen buffer for 698 * @param proposedWidth The proposed width of the offscreen buffer 699 * @param proposedHeight The proposed height of the offscreen buffer 700 * 701 * @return A shared offscreen buffer for painting 702 */ getOffscreenBuffer(Component component, int proposedWidth, int proposedHeight)703 public Image getOffscreenBuffer(Component component, int proposedWidth, 704 int proposedHeight) 705 { 706 Component root = SwingUtilities.getWindowAncestor(component); 707 Image buffer = (Image) offscreenBuffers.get(root); 708 if (buffer == null 709 || buffer.getWidth(null) < proposedWidth 710 || buffer.getHeight(null) < proposedHeight) 711 { 712 int width = Math.max(proposedWidth, root.getWidth()); 713 width = Math.min(doubleBufferMaximumSize.width, width); 714 int height = Math.max(proposedHeight, root.getHeight()); 715 height = Math.min(doubleBufferMaximumSize.height, height); 716 buffer = component.createImage(width, height); 717 offscreenBuffers.put(root, buffer); 718 } 719 return buffer; 720 } 721 722 /** 723 * Blits the back buffer of the specified root component to the screen. 724 * This is package private because it must get called by JComponent. 725 * 726 * @param comp the component to be painted 727 * @param x the area to paint on screen, in comp coordinates 728 * @param y the area to paint on screen, in comp coordinates 729 * @param w the area to paint on screen, in comp coordinates 730 * @param h the area to paint on screen, in comp coordinates 731 */ commitBuffer(Component comp, int x, int y, int w, int h)732 void commitBuffer(Component comp, int x, int y, int w, int h) 733 { 734 Component root = comp; 735 while (root != null 736 && ! (root instanceof Window || root instanceof Applet)) 737 { 738 x += root.getX(); 739 y += root.getY(); 740 root = root.getParent(); 741 } 742 743 if (root != null) 744 { 745 Graphics g = root.getGraphics(); 746 Image buffer = (Image) offscreenBuffers.get(root); 747 if (buffer != null) 748 { 749 // Make sure we have a sane clip at this point. 750 g.clipRect(x, y, w, h); 751 g.drawImage(buffer, 0, 0, root); 752 g.dispose(); 753 } 754 } 755 } 756 757 /** 758 * Creates and returns a volatile offscreen buffer for the specified 759 * component that can be used as a double buffer. The returned image 760 * is a {@link VolatileImage}. Its size will be <code>(proposedWidth, 761 * proposedHeight)</code> except when the maximum double buffer size 762 * has been set in this RepaintManager. 763 * 764 * @param comp the Component for which to create a volatile buffer 765 * @param proposedWidth the proposed width of the buffer 766 * @param proposedHeight the proposed height of the buffer 767 * 768 * @since 1.4 769 * 770 * @see VolatileImage 771 */ getVolatileOffscreenBuffer(Component comp, int proposedWidth, int proposedHeight)772 public Image getVolatileOffscreenBuffer(Component comp, int proposedWidth, 773 int proposedHeight) 774 { 775 Component root = SwingUtilities.getWindowAncestor(comp); 776 Image buffer = (Image) offscreenBuffers.get(root); 777 if (buffer == null 778 || buffer.getWidth(null) < proposedWidth 779 || buffer.getHeight(null) < proposedHeight 780 || !(buffer instanceof VolatileImage)) 781 { 782 int width = Math.max(proposedWidth, root.getWidth()); 783 width = Math.min(doubleBufferMaximumSize.width, width); 784 int height = Math.max(proposedHeight, root.getHeight()); 785 height = Math.min(doubleBufferMaximumSize.height, height); 786 buffer = root.createVolatileImage(width, height); 787 if (buffer != null) 788 offscreenBuffers.put(root, buffer); 789 } 790 return buffer; 791 } 792 793 794 /** 795 * Get the value of the {@link #doubleBufferMaximumSize} property. 796 * 797 * @return The current value of the property 798 * 799 * @see #setDoubleBufferMaximumSize 800 */ getDoubleBufferMaximumSize()801 public Dimension getDoubleBufferMaximumSize() 802 { 803 return doubleBufferMaximumSize; 804 } 805 806 /** 807 * Set the value of the {@link #doubleBufferMaximumSize} property. 808 * 809 * @param size The new value of the property 810 * 811 * @see #getDoubleBufferMaximumSize 812 */ setDoubleBufferMaximumSize(Dimension size)813 public void setDoubleBufferMaximumSize(Dimension size) 814 { 815 doubleBufferMaximumSize = size; 816 } 817 818 /** 819 * Set the value of the {@link #doubleBufferingEnabled} property. 820 * 821 * @param buffer The new value of the property 822 * 823 * @see #isDoubleBufferingEnabled 824 */ setDoubleBufferingEnabled(boolean buffer)825 public void setDoubleBufferingEnabled(boolean buffer) 826 { 827 doubleBufferingEnabled = buffer; 828 } 829 830 /** 831 * Get the value of the {@link #doubleBufferingEnabled} property. 832 * 833 * @return The current value of the property 834 * 835 * @see #setDoubleBufferingEnabled 836 */ isDoubleBufferingEnabled()837 public boolean isDoubleBufferingEnabled() 838 { 839 return doubleBufferingEnabled; 840 } 841 toString()842 public String toString() 843 { 844 return "RepaintManager"; 845 } 846 847 /** 848 * Sends an RepaintManagerEvent to the event queue with the specified 849 * runnable. This is similar to SwingUtilities.invokeLater(), only that the 850 * event is a low priority event in order to defer the execution a little 851 * more. 852 */ invokeLater(Runnable runnable)853 private void invokeLater(Runnable runnable) 854 { 855 Toolkit tk = Toolkit.getDefaultToolkit(); 856 EventQueue evQueue = tk.getSystemEventQueue(); 857 InvocationEvent ev = new RepaintWorkerEvent(evQueue, runnable, null, false); 858 evQueue.postEvent(ev); 859 } 860 } 861