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