Source for javax.swing.RepaintManager

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