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