Source for javax.swing.RepaintManager

   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: }