001/* RepaintManager.java -- 002 Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038 039package javax.swing; 040 041import gnu.classpath.SystemProperties; 042import gnu.java.awt.LowPriorityEvent; 043 044import java.applet.Applet; 045import java.awt.Component; 046import java.awt.Dimension; 047import java.awt.EventQueue; 048import java.awt.Graphics; 049import java.awt.Image; 050import java.awt.Rectangle; 051import java.awt.Toolkit; 052import java.awt.Window; 053import java.awt.event.InvocationEvent; 054import java.awt.image.VolatileImage; 055import java.util.ArrayList; 056import java.util.HashMap; 057import java.util.HashSet; 058import java.util.Iterator; 059import java.util.Set; 060import java.util.WeakHashMap; 061 062/** 063 * <p>The repaint manager holds a set of dirty regions, invalid components, 064 * and a double buffer surface. The dirty regions and invalid components 065 * are used to coalesce multiple revalidate() and repaint() calls in the 066 * component tree into larger groups to be refreshed "all at once"; the 067 * double buffer surface is used by root components to paint 068 * themselves.</p> 069 * 070 * <p>See <a 071 * href="http://java.sun.com/products/jfc/tsc/articles/painting/index.html">this 072 * document</a> for more details.</p> 073 * document</a> for more details.</p> 074 * 075 * @author Roman Kennke (kennke@aicas.com) 076 * @author Graydon Hoare (graydon@redhat.com) 077 * @author Audrius Meskauskas (audriusa@bioinformatics.org) 078 */ 079public class RepaintManager 080{ 081 /** 082 * An InvocationEvent subclass that implements LowPriorityEvent. This is used 083 * to defer the execution of RepaintManager requests as long as possible on 084 * the event queue. This way we make sure that all available input is 085 * processed before getting active with the RepaintManager. This allows 086 * for better optimization (more validate and repaint requests can be 087 * coalesced) and thus has a positive effect on performance for GUI 088 * applications under heavy load. 089 */ 090 private static class RepaintWorkerEvent 091 extends InvocationEvent 092 implements LowPriorityEvent 093 { 094 095 /** 096 * Creates a new RepaintManager event. 097 * 098 * @param source the source 099 * @param runnable the runnable to execute 100 */ 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 */ 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 148 public RepaintWorker() 149 { 150 live = false; 151 } 152 153 public synchronized void setLive(boolean b) 154 { 155 live = b; 156 } 157 158 public synchronized boolean isLive() 159 { 160 return live; 161 } 162 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 837 public boolean isDoubleBufferingEnabled() 838 { 839 return doubleBufferingEnabled; 840 } 841 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 */ 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}