GNU Classpath (0.18) | ||
Frames | No Frames |
1: /* RepaintManager.java -- 2: Copyright (C) 2002, 2004 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 java.awt.Component; 42: import java.awt.Dimension; 43: import java.awt.Image; 44: import java.awt.Rectangle; 45: import java.awt.image.VolatileImage; 46: import java.util.Enumeration; 47: import java.util.HashMap; 48: import java.util.Hashtable; 49: import java.util.Iterator; 50: import java.util.Map; 51: import java.util.Vector; 52: 53: /** 54: * <p>The repaint manager holds a set of dirty regions, invalid components, 55: * and a double buffer surface. The dirty regions and invalid components 56: * are used to coalesce multiple revalidate() and repaint() calls in the 57: * component tree into larger groups to be refreshed "all at once"; the 58: * double buffer surface is used by root components to paint 59: * themselves.</p> 60: * 61: * <p>In general, painting is very confusing in swing. see <a 62: * href="http://java.sun.com/products/jfc/tsc/articles/painting/index.html">this 63: * document</a> for more details.</p> 64: * 65: * @author Graydon Hoare (graydon@redhat.com) 66: */ 67: public class RepaintManager 68: { 69: 70: /** 71: * <p>A helper class which is placed into the system event queue at 72: * various times in order to facilitate repainting and layout. There is 73: * typically only one of these objects active at any time. When the 74: * {@link RepaintManager} is told to queue a repaint, it checks to see if 75: * a {@link RepaintWorker} is "live" in the system event queue, and if 76: * not it inserts one using {@link SwingUtilities#invokeLater}.</p> 77: * 78: * <p>When the {@link RepaintWorker} comes to the head of the system 79: * event queue, its {@link RepaintWorker#run} method is executed by the 80: * swing paint thread, which revalidates all invalid components and 81: * repaints any damage in the swing scene.</p> 82: */ 83: 84: protected class RepaintWorker 85: implements Runnable 86: { 87: boolean live; 88: public RepaintWorker() 89: { 90: live = false; 91: } 92: public synchronized void setLive(boolean b) 93: { 94: live = b; 95: } 96: public synchronized boolean isLive() 97: { 98: return live; 99: } 100: public void run() 101: { 102: RepaintManager rm = RepaintManager.globalManager; 103: setLive(false); 104: rm.validateInvalidComponents(); 105: rm.paintDirtyRegions(); 106: } 107: } 108: 109: 110: /** 111: * A table storing the dirty regions of components. The keys of this 112: * table are components, the values are rectangles. Each component maps 113: * to exactly one rectangle. When more regions are marked as dirty on a 114: * component, they are union'ed with the existing rectangle. 115: * 116: * @see #addDirtyRegion 117: * @see #getDirtyRegion 118: * @see #isCompletelyDirty 119: * @see #markCompletelyClean 120: * @see #markCompletelyDirty 121: */ 122: Hashtable dirtyComponents; 123: 124: /** 125: * A single, shared instance of the helper class. Any methods which mark 126: * components as invalid or dirty eventually activate this instance. It 127: * is added to the event queue if it is not already active, otherwise 128: * reused. 129: * 130: * @see #addDirtyRegion 131: * @see #addInvalidComponent 132: */ 133: RepaintWorker repaintWorker; 134: 135: /** 136: * The set of components which need revalidation, in the "layout" sense. 137: * There is no additional information about "what kind of layout" they 138: * need (as there is with dirty regions), so it is just a vector rather 139: * than a table. 140: * 141: * @see #addInvalidComponent 142: * @see #removeInvalidComponent 143: * @see #validateInvalidComponents 144: */ 145: Vector invalidComponents; 146: 147: /** 148: * Whether or not double buffering is enabled on this repaint 149: * manager. This is merely a hint to clients; the RepaintManager will 150: * always return an offscreen buffer when one is requested. 151: * 152: * @see #getDoubleBufferingEnabled 153: * @see #setDoubleBufferingEnabled 154: */ 155: boolean doubleBufferingEnabled; 156: 157: /** 158: * The current offscreen buffer. This is reused for all requests for 159: * offscreen drawing buffers. It grows as necessary, up to {@link 160: * #doubleBufferMaximumSize}, but there is only one shared instance. 161: * 162: * @see #getOffscreenBuffer 163: * @see #doubleBufferMaximumSize 164: */ 165: Image doubleBuffer; 166: 167: /** 168: * The maximum width and height to allocate as a double buffer. Requests 169: * beyond this size are ignored. 170: * 171: * @see #paintDirtyRegions 172: * @see #getDoubleBufferMaximumSize 173: * @see #setDoubleBufferMaximumSize 174: */ 175: Dimension doubleBufferMaximumSize; 176: 177: 178: /** 179: * The global, shared RepaintManager instance. This is reused for all 180: * components in all windows. This is package-private to avoid an accessor 181: * method. 182: * 183: * @see #currentManager 184: * @see #setCurrentManager 185: */ 186: static RepaintManager globalManager; 187: 188: /** 189: * Create a new RepaintManager object. 190: */ 191: public RepaintManager() 192: { 193: dirtyComponents = new Hashtable(); 194: invalidComponents = new Vector(); 195: repaintWorker = new RepaintWorker(); 196: doubleBufferMaximumSize = new Dimension(2000,2000); 197: doubleBufferingEnabled = true; 198: } 199: 200: /** 201: * Get the value of the shared {@link #globalManager} instance, possibly 202: * returning a special manager associated with the specified 203: * component. The default implementaiton ignores the component parameter. 204: * 205: * @param component A component to look up the manager of 206: * 207: * @return The current repaint manager 208: * 209: * @see #setCurrentManager 210: */ 211: public static RepaintManager currentManager(Component component) 212: { 213: if (globalManager == null) 214: globalManager = new RepaintManager(); 215: return globalManager; 216: } 217: 218: /** 219: * Get the value of the shared {@link #globalManager} instance, possibly 220: * returning a special manager associated with the specified 221: * component. The default implementaiton ignores the component parameter. 222: * 223: * @param component A component to look up the manager of 224: * 225: * @return The current repaint manager 226: * 227: * @see #setCurrentManager 228: */ 229: public static RepaintManager currentManager(JComponent component) 230: { 231: return currentManager((Component)component); 232: } 233: 234: /** 235: * Set the value of the shared {@link #globalManager} instance. 236: * 237: * @param manager The new value of the shared instance 238: * 239: * @see #currentManager(JComponent) 240: */ 241: public static void setCurrentManager(RepaintManager manager) 242: { 243: globalManager = manager; 244: } 245: 246: /** 247: * Add a component to the {@link #invalidComponents} vector. If the 248: * {@link #repaintWorker} class is not active, insert it in the system 249: * event queue. 250: * 251: * @param component The component to add 252: * 253: * @see #removeInvalidComponent 254: */ 255: public synchronized void addInvalidComponent(JComponent component) 256: { 257: Component ancestor = component.getParent(); 258: 259: while (ancestor != null 260: && (! (ancestor instanceof JComponent) 261: || ! ((JComponent) ancestor).isValidateRoot() )) 262: ancestor = ancestor.getParent(); 263: 264: if (ancestor != null 265: && ancestor instanceof JComponent 266: && ((JComponent) ancestor).isValidateRoot()) 267: component = (JComponent) ancestor; 268: 269: if (invalidComponents.contains(component)) 270: return; 271: 272: invalidComponents.add(component); 273: 274: if (! repaintWorker.isLive()) 275: { 276: repaintWorker.setLive(true); 277: SwingUtilities.invokeLater(repaintWorker); 278: } 279: } 280: 281: /** 282: * Remove a component from the {@link #invalidComponents} vector. 283: * 284: * @param component The component to remove 285: * 286: * @see #addInvalidComponent 287: */ 288: public synchronized void removeInvalidComponent(JComponent component) 289: { 290: invalidComponents.removeElement(component); 291: } 292: 293: /** 294: * Add a region to the set of dirty regions for a specified component. 295: * This involves union'ing the new region with any existing dirty region 296: * associated with the component. If the {@link #repaintWorker} class 297: * is not active, insert it in the system event queue. 298: * 299: * @param component The component to add a dirty region for 300: * @param x The left x coordinate of the new dirty region 301: * @param y The top y coordinate of the new dirty region 302: * @param w The width of the new dirty region 303: * @param h The height of the new dirty region 304: * 305: * @see #addDirtyRegion 306: * @see #getDirtyRegion 307: * @see #isCompletelyDirty 308: * @see #markCompletelyClean 309: * @see #markCompletelyDirty 310: */ 311: public synchronized void addDirtyRegion(JComponent component, int x, int y, 312: int w, int h) 313: { 314: if (w == 0 || h == 0) 315: return; 316: 317: Rectangle r = new Rectangle(x, y, w, h); 318: if (dirtyComponents.containsKey(component)) 319: r = r.union((Rectangle)dirtyComponents.get(component)); 320: dirtyComponents.put(component, r); 321: if (! repaintWorker.isLive()) 322: { 323: repaintWorker.setLive(true); 324: SwingUtilities.invokeLater(repaintWorker); 325: } 326: } 327: 328: /** 329: * Get the dirty region associated with a component, or <code>null</code> 330: * if the component has no dirty region. 331: * 332: * @param component The component to get the dirty region of 333: * 334: * @return The dirty region of the component 335: * 336: * @see #dirtyComponents 337: * @see #addDirtyRegion 338: * @see #isCompletelyDirty 339: * @see #markCompletelyClean 340: * @see #markCompletelyDirty 341: */ 342: public Rectangle getDirtyRegion(JComponent component) 343: { 344: return (Rectangle) dirtyComponents.get(component); 345: } 346: 347: /** 348: * Mark a component as dirty over its entire bounds. 349: * 350: * @param component The component to mark as dirty 351: * 352: * @see #dirtyComponents 353: * @see #addDirtyRegion 354: * @see #getDirtyRegion 355: * @see #isCompletelyDirty 356: * @see #markCompletelyClean 357: */ 358: public void markCompletelyDirty(JComponent component) 359: { 360: Rectangle r = component.getBounds(); 361: addDirtyRegion(component, r.x, r.y, r.width, r.height); 362: } 363: 364: /** 365: * Remove all dirty regions for a specified component 366: * 367: * @param component The component to mark as clean 368: * 369: * @see #dirtyComponents 370: * @see #addDirtyRegion 371: * @see #getDirtyRegion 372: * @see #isCompletelyDirty 373: * @see #markCompletelyDirty 374: */ 375: public void markCompletelyClean(JComponent component) 376: { 377: dirtyComponents.remove(component); 378: } 379: 380: /** 381: * Return <code>true</code> if the specified component is completely 382: * contained within its dirty region, otherwise <code>false</code> 383: * 384: * @param component The component to check for complete dirtyness 385: * 386: * @return Whether the component is completely dirty 387: * 388: * @see #dirtyComponents 389: * @see #addDirtyRegion 390: * @see #getDirtyRegion 391: * @see #isCompletelyDirty 392: * @see #markCompletelyClean 393: */ 394: public boolean isCompletelyDirty(JComponent component) 395: { 396: Rectangle dirty = (Rectangle) dirtyComponents.get(component); 397: if (dirty == null) 398: return false; 399: Rectangle r = component.getBounds(); 400: if (r == null) 401: return true; 402: return dirty.contains(r); 403: } 404: 405: /** 406: * Validate all components which have been marked invalid in the {@link 407: * #invalidComponents} vector. 408: */ 409: public void validateInvalidComponents() 410: { 411: for (Enumeration e = invalidComponents.elements(); e.hasMoreElements(); ) 412: { 413: JComponent comp = (JComponent) e.nextElement(); 414: if (! (comp.isVisible() && comp.isShowing())) 415: continue; 416: comp.validate(); 417: } 418: invalidComponents.clear(); 419: } 420: 421: /** 422: * Repaint all regions of all components which have been marked dirty in 423: * the {@link #dirtyComponents} table. 424: */ 425: public void paintDirtyRegions() 426: { 427: // step 1: pull out roots and calculate spanning damage 428: 429: HashMap roots = new HashMap(); 430: for (Enumeration e = dirtyComponents.keys(); e.hasMoreElements(); ) 431: { 432: JComponent comp = (JComponent) e.nextElement(); 433: if (! (comp.isVisible() && comp.isShowing())) 434: continue; 435: Rectangle damaged = getDirtyRegion(comp); 436: if (damaged.width == 0 || damaged.height == 0) 437: continue; 438: JRootPane root = comp.getRootPane(); 439: // If the component has no root, no repainting will occur. 440: if (root == null) 441: continue; 442: Rectangle rootDamage = SwingUtilities.convertRectangle(comp, damaged, root); 443: if (! roots.containsKey(root)) 444: { 445: roots.put(root, rootDamage); 446: } 447: else 448: { 449: roots.put(root, ((Rectangle)roots.get(root)).union(rootDamage)); 450: } 451: } 452: dirtyComponents.clear(); 453: 454: // step 2: paint those roots 455: Iterator i = roots.entrySet().iterator(); 456: while(i.hasNext()) 457: { 458: Map.Entry ent = (Map.Entry) i.next(); 459: JRootPane root = (JRootPane) ent.getKey(); 460: Rectangle rect = (Rectangle) ent.getValue(); 461: root.paintImmediately(rect); 462: } 463: } 464: 465: /** 466: * Get an offscreen buffer for painting a component's image. This image 467: * may be smaller than the proposed dimensions, depending on the value of 468: * the {@link #doubleBufferMaximumSize} property. 469: * 470: * @param component The component to return an offscreen buffer for 471: * @param proposedWidth The proposed width of the offscreen buffer 472: * @param proposedHeight The proposed height of the offscreen buffer 473: * 474: * @return A shared offscreen buffer for painting 475: * 476: * @see #doubleBuffer 477: */ 478: public Image getOffscreenBuffer(Component component, int proposedWidth, 479: int proposedHeight) 480: { 481: if (doubleBuffer == null 482: || (((doubleBuffer.getWidth(null) < proposedWidth) 483: || (doubleBuffer.getHeight(null) < proposedHeight)) 484: && (proposedWidth < doubleBufferMaximumSize.width) 485: && (proposedHeight < doubleBufferMaximumSize.height))) 486: { 487: doubleBuffer = component.createImage(proposedWidth, proposedHeight); 488: } 489: return doubleBuffer; 490: } 491: 492: /** 493: * Creates and returns a volatile offscreen buffer for the specified 494: * component that can be used as a double buffer. The returned image 495: * is a {@link VolatileImage}. Its size will be <code>(proposedWidth, 496: * proposedHeight)</code> except when the maximum double buffer size 497: * has been set in this RepaintManager. 498: * 499: * @param comp the Component for which to create a volatile buffer 500: * @param proposedWidth the proposed width of the buffer 501: * @param proposedHeight the proposed height of the buffer 502: * 503: * @since 1.4 504: * 505: * @see VolatileImage 506: */ 507: public Image getVolatileOffscreenBuffer(Component comp, int proposedWidth, 508: int proposedHeight) 509: { 510: int maxWidth = doubleBufferMaximumSize.width; 511: int maxHeight = doubleBufferMaximumSize.height; 512: return comp.createVolatileImage(Math.min(maxWidth, proposedWidth), 513: Math.min(maxHeight, proposedHeight)); 514: } 515: 516: 517: /** 518: * Get the value of the {@link #doubleBufferMaximumSize} property. 519: * 520: * @return The current value of the property 521: * 522: * @see #setDoubleBufferMaximumSize 523: */ 524: public Dimension getDoubleBufferMaximumSize() 525: { 526: return doubleBufferMaximumSize; 527: } 528: 529: /** 530: * Set the value of the {@link #doubleBufferMaximumSize} property. 531: * 532: * @param size The new value of the property 533: * 534: * @see #getDoubleBufferMaximumSize 535: */ 536: public void setDoubleBufferMaximumSize(Dimension size) 537: { 538: doubleBufferMaximumSize = size; 539: } 540: 541: /** 542: * Set the value of the {@link #doubleBufferingEnabled} property. 543: * 544: * @param buffer The new value of the property 545: * 546: * @see #isDoubleBufferingEnabled 547: */ 548: public void setDoubleBufferingEnabled(boolean buffer) 549: { 550: doubleBufferingEnabled = buffer; 551: } 552: 553: /** 554: * Get the value of the {@link #doubleBufferingEnabled} property. 555: * 556: * @return The current value of the property 557: * 558: * @see #setDoubleBufferingEnabled 559: */ 560: public boolean isDoubleBufferingEnabled() 561: { 562: return doubleBufferingEnabled; 563: } 564: 565: public String toString() 566: { 567: return "RepaintManager"; 568: } 569: }
GNU Classpath (0.18) |