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