GNU Classpath (0.19) | ||
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 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.ArrayList; 47: import java.util.Collections; 48: import java.util.Comparator; 49: import java.util.HashMap; 50: import java.util.Iterator; 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: protected class RepaintWorker 83: implements Runnable 84: { 85: 86: boolean live; 87: 88: public RepaintWorker() 89: { 90: live = false; 91: } 92: 93: public synchronized void setLive(boolean b) 94: { 95: live = b; 96: } 97: 98: public synchronized boolean isLive() 99: { 100: return live; 101: } 102: 103: public void run() 104: { 105: RepaintManager rm = RepaintManager.globalManager; 106: setLive(false); 107: rm.validateInvalidComponents(); 108: rm.paintDirtyRegions(); 109: } 110: 111: } 112: 113: /** 114: * Compares two components using their depths in the component hierarchy. 115: * A component with a lesser depth (higher level components) are sorted 116: * before components with a deeper depth (low level components). This is used 117: * to order paint requests, so that the higher level components are painted 118: * before the low level components get painted. 119: * 120: * @author Roman Kennke (kennke@aicas.com) 121: */ 122: private class ComponentComparator implements Comparator 123: { 124: 125: /** 126: * Compares two components. 127: * 128: * @param o1 the first component 129: * @param o2 the second component 130: * 131: * @return a negative integer, if <code>o1</code> is higher in the 132: * hierarchy than <code>o2</code>, zero, if both are at the same 133: * level and a positive integer, if <code>o1</code> is deeper in 134: * the hierarchy than <code>o2</code> 135: */ 136: public int compare(Object o1, Object o2) 137: { 138: if (o1 instanceof JComponent && o2 instanceof JComponent) 139: { 140: JComponent c1 = (JComponent) o1; 141: JComponent c2 = (JComponent) o2; 142: return getDepth(c1) - getDepth(c2); 143: } 144: else 145: throw new ClassCastException("This comparator can only be used with " 146: + "JComponents"); 147: } 148: 149: /** 150: * Computes the depth for a given JComponent. 151: * 152: * @param c the component to compute the depth for 153: * 154: * @return the depth of the component 155: */ 156: private int getDepth(JComponent c) 157: { 158: Component comp = c; 159: int depth = 0; 160: while (comp != null) 161: { 162: comp = comp.getParent(); 163: depth++; 164: } 165: return depth; 166: } 167: } 168: 169: /** 170: * A table storing the dirty regions of components. The keys of this 171: * table are components, the values are rectangles. Each component maps 172: * to exactly one rectangle. When more regions are marked as dirty on a 173: * component, they are union'ed with the existing rectangle. 174: * 175: * @see #addDirtyRegion 176: * @see #getDirtyRegion 177: * @see #isCompletelyDirty 178: * @see #markCompletelyClean 179: * @see #markCompletelyDirty 180: */ 181: HashMap dirtyComponents; 182: 183: HashMap workDirtyComponents; 184: 185: /** 186: * Stores the order in which the components get repainted. 187: */ 188: ArrayList repaintOrder; 189: ArrayList workRepaintOrder; 190: 191: /** 192: * The comparator used for ordered inserting into the repaintOrder list. 193: */ 194: Comparator comparator; 195: 196: /** 197: * A single, shared instance of the helper class. Any methods which mark 198: * components as invalid or dirty eventually activate this instance. It 199: * is added to the event queue if it is not already active, otherwise 200: * reused. 201: * 202: * @see #addDirtyRegion 203: * @see #addInvalidComponent 204: */ 205: RepaintWorker repaintWorker; 206: 207: /** 208: * The set of components which need revalidation, in the "layout" sense. 209: * There is no additional information about "what kind of layout" they 210: * need (as there is with dirty regions), so it is just a vector rather 211: * than a table. 212: * 213: * @see #addInvalidComponent 214: * @see #removeInvalidComponent 215: * @see #validateInvalidComponents 216: */ 217: ArrayList invalidComponents; 218: ArrayList workInvalidComponents; 219: 220: /** 221: * Whether or not double buffering is enabled on this repaint 222: * manager. This is merely a hint to clients; the RepaintManager will 223: * always return an offscreen buffer when one is requested. 224: * 225: * @see #isDoubleBufferingEnabled 226: * @see #setDoubleBufferingEnabled 227: */ 228: boolean doubleBufferingEnabled; 229: 230: /** 231: * The current offscreen buffer. This is reused for all requests for 232: * offscreen drawing buffers. It grows as necessary, up to {@link 233: * #doubleBufferMaximumSize}, but there is only one shared instance. 234: * 235: * @see #getOffscreenBuffer 236: * @see #doubleBufferMaximumSize 237: */ 238: Image doubleBuffer; 239: 240: /** 241: * The maximum width and height to allocate as a double buffer. Requests 242: * beyond this size are ignored. 243: * 244: * @see #paintDirtyRegions 245: * @see #getDoubleBufferMaximumSize 246: * @see #setDoubleBufferMaximumSize 247: */ 248: Dimension doubleBufferMaximumSize; 249: 250: 251: /** 252: * The global, shared RepaintManager instance. This is reused for all 253: * components in all windows. This is package-private to avoid an accessor 254: * method. 255: * 256: * @see #currentManager(JComponent) 257: * @see #setCurrentManager 258: */ 259: static RepaintManager globalManager; 260: 261: /** 262: * Create a new RepaintManager object. 263: */ 264: public RepaintManager() 265: { 266: dirtyComponents = new HashMap(); 267: workDirtyComponents = new HashMap(); 268: repaintOrder = new ArrayList(); 269: workRepaintOrder = new ArrayList(); 270: invalidComponents = new ArrayList(); 271: workInvalidComponents = new ArrayList(); 272: repaintWorker = new RepaintWorker(); 273: doubleBufferMaximumSize = new Dimension(2000,2000); 274: doubleBufferingEnabled = true; 275: } 276: 277: /** 278: * Get the value of the shared {@link #globalManager} instance, possibly 279: * returning a special manager associated with the specified 280: * component. The default implementaiton ignores the component parameter. 281: * 282: * @param component A component to look up the manager of 283: * 284: * @return The current repaint manager 285: * 286: * @see #setCurrentManager 287: */ 288: public static RepaintManager currentManager(Component component) 289: { 290: if (globalManager == null) 291: globalManager = new RepaintManager(); 292: return globalManager; 293: } 294: 295: /** 296: * Get the value of the shared {@link #globalManager} instance, possibly 297: * returning a special manager associated with the specified 298: * component. The default implementaiton ignores the component parameter. 299: * 300: * @param component A component to look up the manager of 301: * 302: * @return The current repaint manager 303: * 304: * @see #setCurrentManager 305: */ 306: public static RepaintManager currentManager(JComponent component) 307: { 308: return currentManager((Component)component); 309: } 310: 311: /** 312: * Set the value of the shared {@link #globalManager} instance. 313: * 314: * @param manager The new value of the shared instance 315: * 316: * @see #currentManager(JComponent) 317: */ 318: public static void setCurrentManager(RepaintManager manager) 319: { 320: globalManager = manager; 321: } 322: 323: /** 324: * Add a component to the {@link #invalidComponents} vector. If the 325: * {@link #repaintWorker} class is not active, insert it in the system 326: * event queue. 327: * 328: * @param component The component to add 329: * 330: * @see #removeInvalidComponent 331: */ 332: public synchronized void addInvalidComponent(JComponent component) 333: { 334: Component ancestor = component.getParent(); 335: 336: while (ancestor != null 337: && (! (ancestor instanceof JComponent) 338: || ! ((JComponent) ancestor).isValidateRoot() )) 339: ancestor = ancestor.getParent(); 340: 341: if (ancestor != null 342: && ancestor instanceof JComponent 343: && ((JComponent) ancestor).isValidateRoot()) 344: component = (JComponent) ancestor; 345: 346: if (invalidComponents.contains(component)) 347: return; 348: 349: invalidComponents.add(component); 350: 351: if (! repaintWorker.isLive()) 352: { 353: repaintWorker.setLive(true); 354: SwingUtilities.invokeLater(repaintWorker); 355: } 356: } 357: 358: /** 359: * Remove a component from the {@link #invalidComponents} vector. 360: * 361: * @param component The component to remove 362: * 363: * @see #addInvalidComponent 364: */ 365: public synchronized void removeInvalidComponent(JComponent component) 366: { 367: invalidComponents.remove(component); 368: } 369: 370: /** 371: * Add a region to the set of dirty regions for a specified component. 372: * This involves union'ing the new region with any existing dirty region 373: * associated with the component. If the {@link #repaintWorker} class 374: * is not active, insert it in the system event queue. 375: * 376: * @param component The component to add a dirty region for 377: * @param x The left x coordinate of the new dirty region 378: * @param y The top y coordinate of the new dirty region 379: * @param w The width of the new dirty region 380: * @param h The height of the new dirty region 381: * 382: * @see #addDirtyRegion 383: * @see #getDirtyRegion 384: * @see #isCompletelyDirty 385: * @see #markCompletelyClean 386: * @see #markCompletelyDirty 387: */ 388: public synchronized void addDirtyRegion(JComponent component, int x, int y, 389: int w, int h) 390: { 391: if (w == 0 || h == 0 || !component.isShowing()) 392: return; 393: Rectangle r = new Rectangle(x, y, w, h); 394: if (dirtyComponents.containsKey(component)) 395: r = r.union((Rectangle)dirtyComponents.get(component)); 396: else 397: insertInRepaintOrder(component); 398: dirtyComponents.put(component, r); 399: if (! repaintWorker.isLive()) 400: { 401: repaintWorker.setLive(true); 402: SwingUtilities.invokeLater(repaintWorker); 403: } 404: } 405: 406: /** 407: * Inserts a component into the repaintOrder list in an ordered fashion, 408: * using a binary search. 409: * 410: * @param c the component to be inserted 411: */ 412: private void insertInRepaintOrder(JComponent c) 413: { 414: if (comparator == null) 415: comparator = new ComponentComparator(); 416: int insertIndex = Collections.binarySearch(repaintOrder, c, comparator); 417: if (insertIndex < 0) 418: insertIndex = -(insertIndex + 1); 419: repaintOrder.add(insertIndex, c); 420: } 421: 422: /** 423: * Get the dirty region associated with a component, or <code>null</code> 424: * if the component has no dirty region. 425: * 426: * @param component The component to get the dirty region of 427: * 428: * @return The dirty region of the component 429: * 430: * @see #dirtyComponents 431: * @see #addDirtyRegion 432: * @see #isCompletelyDirty 433: * @see #markCompletelyClean 434: * @see #markCompletelyDirty 435: */ 436: public Rectangle getDirtyRegion(JComponent component) 437: { 438: Rectangle dirty = (Rectangle) dirtyComponents.get(component); 439: if (dirty == null) 440: dirty = new Rectangle(); 441: return dirty; 442: } 443: 444: /** 445: * Mark a component as dirty over its entire bounds. 446: * 447: * @param component The component to mark as dirty 448: * 449: * @see #dirtyComponents 450: * @see #addDirtyRegion 451: * @see #getDirtyRegion 452: * @see #isCompletelyDirty 453: * @see #markCompletelyClean 454: */ 455: public void markCompletelyDirty(JComponent component) 456: { 457: Rectangle r = component.getBounds(); 458: addDirtyRegion(component, r.x, r.y, r.width, r.height); 459: component.isCompletelyDirty = true; 460: } 461: 462: /** 463: * Remove all dirty regions for a specified component 464: * 465: * @param component The component to mark as clean 466: * 467: * @see #dirtyComponents 468: * @see #addDirtyRegion 469: * @see #getDirtyRegion 470: * @see #isCompletelyDirty 471: * @see #markCompletelyDirty 472: */ 473: public void markCompletelyClean(JComponent component) 474: { 475: synchronized (this) 476: { 477: dirtyComponents.remove(component); 478: } 479: component.isCompletelyDirty = false; 480: } 481: 482: /** 483: * Return <code>true</code> if the specified component is completely 484: * contained within its dirty region, otherwise <code>false</code> 485: * 486: * @param component The component to check for complete dirtyness 487: * 488: * @return Whether the component is completely dirty 489: * 490: * @see #dirtyComponents 491: * @see #addDirtyRegion 492: * @see #getDirtyRegion 493: * @see #isCompletelyDirty 494: * @see #markCompletelyClean 495: */ 496: public boolean isCompletelyDirty(JComponent component) 497: { 498: if (! dirtyComponents.containsKey(component)) 499: return false; 500: return component.isCompletelyDirty; 501: } 502: 503: /** 504: * Validate all components which have been marked invalid in the {@link 505: * #invalidComponents} vector. 506: */ 507: public void validateInvalidComponents() 508: { 509: // In order to keep the blocking of application threads minimal, we switch 510: // the invalidComponents field with the workInvalidComponents field and 511: // work with the workInvalidComponents field. 512: synchronized(this) 513: { 514: ArrayList swap = invalidComponents; 515: invalidComponents = workInvalidComponents; 516: workInvalidComponents = swap; 517: } 518: for (Iterator i = workInvalidComponents.iterator(); i.hasNext(); ) 519: { 520: JComponent comp = (JComponent) i.next(); 521: if (! (comp.isVisible() && comp.isShowing())) 522: continue; 523: comp.validate(); 524: } 525: workInvalidComponents.clear(); 526: } 527: 528: /** 529: * Repaint all regions of all components which have been marked dirty in 530: * the {@link #dirtyComponents} table. 531: */ 532: public synchronized void paintDirtyRegions() 533: { 534: // In order to keep the blocking of application threads minimal, we switch 535: // the dirtyComponents field with the workdirtyComponents field and the 536: // repaintOrder field with the workRepaintOrder field and work with the 537: // work* fields. 538: synchronized(this) 539: { 540: ArrayList swap = workRepaintOrder; 541: workRepaintOrder = repaintOrder; 542: repaintOrder = swap; 543: HashMap swap2 = workDirtyComponents; 544: workDirtyComponents = dirtyComponents; 545: dirtyComponents = swap2; 546: } 547: for (Iterator i = workRepaintOrder.iterator(); i.hasNext();) 548: { 549: JComponent comp = (JComponent) i.next(); 550: // If a component is marked completely clean in the meantime, then skip 551: // it. 552: Rectangle damaged = (Rectangle) workDirtyComponents.get(comp); 553: if (damaged == null || damaged.isEmpty()) 554: continue; 555: comp.paintImmediately(damaged); 556: } 557: workRepaintOrder.clear(); 558: workDirtyComponents.clear(); 559: } 560: 561: /** 562: * Get an offscreen buffer for painting a component's image. This image 563: * may be smaller than the proposed dimensions, depending on the value of 564: * the {@link #doubleBufferMaximumSize} property. 565: * 566: * @param component The component to return an offscreen buffer for 567: * @param proposedWidth The proposed width of the offscreen buffer 568: * @param proposedHeight The proposed height of the offscreen buffer 569: * 570: * @return A shared offscreen buffer for painting 571: * 572: * @see #doubleBuffer 573: */ 574: public Image getOffscreenBuffer(Component component, int proposedWidth, 575: int proposedHeight) 576: { 577: if (doubleBuffer == null 578: || (((doubleBuffer.getWidth(null) < proposedWidth) 579: || (doubleBuffer.getHeight(null) < proposedHeight)) 580: && (proposedWidth < doubleBufferMaximumSize.width) 581: && (proposedHeight < doubleBufferMaximumSize.height))) 582: { 583: doubleBuffer = component.createImage(proposedWidth, proposedHeight); 584: } 585: return doubleBuffer; 586: } 587: 588: /** 589: * Creates and returns a volatile offscreen buffer for the specified 590: * component that can be used as a double buffer. The returned image 591: * is a {@link VolatileImage}. Its size will be <code>(proposedWidth, 592: * proposedHeight)</code> except when the maximum double buffer size 593: * has been set in this RepaintManager. 594: * 595: * @param comp the Component for which to create a volatile buffer 596: * @param proposedWidth the proposed width of the buffer 597: * @param proposedHeight the proposed height of the buffer 598: * 599: * @since 1.4 600: * 601: * @see VolatileImage 602: */ 603: public Image getVolatileOffscreenBuffer(Component comp, int proposedWidth, 604: int proposedHeight) 605: { 606: int maxWidth = doubleBufferMaximumSize.width; 607: int maxHeight = doubleBufferMaximumSize.height; 608: return comp.createVolatileImage(Math.min(maxWidth, proposedWidth), 609: Math.min(maxHeight, proposedHeight)); 610: } 611: 612: 613: /** 614: * Get the value of the {@link #doubleBufferMaximumSize} property. 615: * 616: * @return The current value of the property 617: * 618: * @see #setDoubleBufferMaximumSize 619: */ 620: public Dimension getDoubleBufferMaximumSize() 621: { 622: return doubleBufferMaximumSize; 623: } 624: 625: /** 626: * Set the value of the {@link #doubleBufferMaximumSize} property. 627: * 628: * @param size The new value of the property 629: * 630: * @see #getDoubleBufferMaximumSize 631: */ 632: public void setDoubleBufferMaximumSize(Dimension size) 633: { 634: doubleBufferMaximumSize = size; 635: } 636: 637: /** 638: * Set the value of the {@link #doubleBufferingEnabled} property. 639: * 640: * @param buffer The new value of the property 641: * 642: * @see #isDoubleBufferingEnabled 643: */ 644: public void setDoubleBufferingEnabled(boolean buffer) 645: { 646: doubleBufferingEnabled = buffer; 647: } 648: 649: /** 650: * Get the value of the {@link #doubleBufferingEnabled} property. 651: * 652: * @return The current value of the property 653: * 654: * @see #setDoubleBufferingEnabled 655: */ 656: public boolean isDoubleBufferingEnabled() 657: { 658: return doubleBufferingEnabled; 659: } 660: 661: public String toString() 662: { 663: return "RepaintManager"; 664: } 665: }
GNU Classpath (0.19) |