Frames | No Frames |
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: */ 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: }