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