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