RepaintManager.java 26.7 KB
Newer Older
Tom Tromey committed
1
/* RepaintManager.java --
2
   Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
Tom Tromey committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */


package javax.swing;

41
import java.applet.Applet;
Tom Tromey committed
42 43
import java.awt.Component;
import java.awt.Dimension;
44
import java.awt.Graphics;
Tom Tromey committed
45 46
import java.awt.Image;
import java.awt.Rectangle;
47
import java.awt.Window;
Tom Tromey committed
48
import java.awt.image.VolatileImage;
49
import java.util.ArrayList;
Tom Tromey committed
50
import java.util.HashMap;
51
import java.util.HashSet;
Tom Tromey committed
52
import java.util.Iterator;
53 54
import java.util.Map;
import java.util.Set;
55
import java.util.WeakHashMap;
Tom Tromey committed
56 57 58 59 60 61 62 63 64

/**
 * <p>The repaint manager holds a set of dirty regions, invalid components,
 * and a double buffer surface.  The dirty regions and invalid components
 * are used to coalesce multiple revalidate() and repaint() calls in the
 * component tree into larger groups to be refreshed "all at once"; the
 * double buffer surface is used by root components to paint
 * themselves.</p>
 *
65
 * <p>See <a
Tom Tromey committed
66 67 68
 * href="http://java.sun.com/products/jfc/tsc/articles/painting/index.html">this
 * document</a> for more details.</p>
 *
69
 * @author Roman Kennke (kennke@aicas.com)
Tom Tromey committed
70
 * @author Graydon Hoare (graydon@redhat.com)
71
 * @author Audrius Meskauskas (audriusa@bioinformatics.org)
Tom Tromey committed
72 73 74
 */
public class RepaintManager
{
75 76 77
  /**
   * The current repaint managers, indexed by their ThreadGroups.
   */
78
  static WeakHashMap currentRepaintManagers;
79 80 81 82 83 84

  /**
   * A rectangle object to be reused in damaged regions calculation.
   */
  private static Rectangle rectCache = new Rectangle();

Tom Tromey committed
85 86 87 88 89 90
  /**
   * <p>A helper class which is placed into the system event queue at
   * various times in order to facilitate repainting and layout. There is
   * typically only one of these objects active at any time. When the
   * {@link RepaintManager} is told to queue a repaint, it checks to see if
   * a {@link RepaintWorker} is "live" in the system event queue, and if
Tom Tromey committed
91
   * not it inserts one using {@link SwingUtilities#invokeLater}.</p>
Tom Tromey committed
92 93 94 95 96 97
   *
   * <p>When the {@link RepaintWorker} comes to the head of the system
   * event queue, its {@link RepaintWorker#run} method is executed by the
   * swing paint thread, which revalidates all invalid components and
   * repaints any damage in the swing scene.</p>
   */
98
  private class RepaintWorker
Tom Tromey committed
99 100
    implements Runnable
  {
101

Tom Tromey committed
102
    boolean live;
103

Tom Tromey committed
104 105 106 107
    public RepaintWorker()
    {
      live = false;
    }
108

Tom Tromey committed
109 110 111 112
    public synchronized void setLive(boolean b) 
    {
      live = b;
    }
113

Tom Tromey committed
114 115 116 117
    public synchronized boolean isLive()
    {
      return live;
    }
118

Tom Tromey committed
119 120
    public void run()
    {
121 122 123 124 125 126 127 128 129 130 131 132
      try
        {
          ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
          RepaintManager rm =
            (RepaintManager) currentRepaintManagers.get(threadGroup);
          rm.validateInvalidComponents();
          rm.paintDirtyRegions();
        }
      finally
        {
          setLive(false);
        }
Tom Tromey committed
133
    }
134 135 136

  }

Tom Tromey committed
137 138 139 140 141 142
  /** 
   * A table storing the dirty regions of components.  The keys of this
   * table are components, the values are rectangles. Each component maps
   * to exactly one rectangle.  When more regions are marked as dirty on a
   * component, they are union'ed with the existing rectangle.
   *
143 144 145
   * This is package private to avoid a synthetic accessor method in inner
   * class.
   *
Tom Tromey committed
146 147 148 149 150 151
   * @see #addDirtyRegion
   * @see #getDirtyRegion
   * @see #isCompletelyDirty
   * @see #markCompletelyClean
   * @see #markCompletelyDirty
   */
152
  private HashMap dirtyComponents;
153 154

  /**
155 156 157
   * The dirtyComponents which is used in paintDiryRegions to avoid unnecessary
   * locking.
   */
158
  private HashMap dirtyComponentsWork;
Tom Tromey committed
159 160 161 162 163 164 165 166 167 168

  /**
   * A single, shared instance of the helper class. Any methods which mark
   * components as invalid or dirty eventually activate this instance. It
   * is added to the event queue if it is not already active, otherwise
   * reused.
   *
   * @see #addDirtyRegion
   * @see #addInvalidComponent
   */
169
  private RepaintWorker repaintWorker;
Tom Tromey committed
170 171 172 173 174 175 176 177 178 179 180

  /** 
   * The set of components which need revalidation, in the "layout" sense.
   * There is no additional information about "what kind of layout" they
   * need (as there is with dirty regions), so it is just a vector rather
   * than a table.
   *
   * @see #addInvalidComponent
   * @see #removeInvalidComponent
   * @see #validateInvalidComponents
   */
181
  private ArrayList invalidComponents;
Tom Tromey committed
182 183 184 185 186 187

  /** 
   * Whether or not double buffering is enabled on this repaint
   * manager. This is merely a hint to clients; the RepaintManager will
   * always return an offscreen buffer when one is requested.
   * 
188
   * @see #isDoubleBufferingEnabled
Tom Tromey committed
189 190
   * @see #setDoubleBufferingEnabled
   */
191
  private boolean doubleBufferingEnabled;
Tom Tromey committed
192

193 194 195 196
  /**
   * The offscreen buffers. This map holds one offscreen buffer per
   * Window/Applet and releases them as soon as the Window/Applet gets garbage
   * collected.
Tom Tromey committed
197
   */
198 199 200 201 202 203 204 205 206 207 208 209 210 211
  private WeakHashMap offscreenBuffers;

  /**
   * Indicates if the RepaintManager is currently repainting an area.
   */
  private boolean repaintUnderway;

  /**
   * This holds buffer commit requests when the RepaintManager is working.
   * This maps Component objects (the top level components) to Rectangle
   * objects (the area of the corresponding buffer that must be blitted on
   * the component).
   */
  private HashMap commitRequests;
Tom Tromey committed
212 213 214 215 216 217 218 219 220

  /**
   * The maximum width and height to allocate as a double buffer. Requests
   * beyond this size are ignored.
   *
   * @see #paintDirtyRegions
   * @see #getDoubleBufferMaximumSize
   * @see #setDoubleBufferMaximumSize
   */
221
  private Dimension doubleBufferMaximumSize;
Tom Tromey committed
222 223 224 225 226 227 228


  /**
   * Create a new RepaintManager object.
   */
  public RepaintManager()
  {
229
    dirtyComponents = new HashMap();
230
    dirtyComponentsWork = new HashMap();
231
    invalidComponents = new ArrayList();
Tom Tromey committed
232 233 234
    repaintWorker = new RepaintWorker();
    doubleBufferMaximumSize = new Dimension(2000,2000);
    doubleBufferingEnabled = true;
235 236 237
    offscreenBuffers = new WeakHashMap();
    repaintUnderway = false;
    commitRequests = new HashMap();
Tom Tromey committed
238 239 240
  }

  /**
241 242 243 244
   * Returns the <code>RepaintManager</code> for the current thread's
   * thread group. The default implementation ignores the
   * <code>component</code> parameter and returns the same repaint manager
   * for all components.
Tom Tromey committed
245
   *
246
   * @param component a component to look up the manager of
Tom Tromey committed
247
   *
248 249
   * @return the current repaint manager for the calling thread's thread group
   *         and the specified component
Tom Tromey committed
250 251 252 253 254
   *
   * @see #setCurrentManager
   */
  public static RepaintManager currentManager(Component component)
  {
255
    if (currentRepaintManagers == null)
256
      currentRepaintManagers = new WeakHashMap();
257 258 259 260 261 262 263 264 265
    ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
    RepaintManager currentManager =
      (RepaintManager) currentRepaintManagers.get(threadGroup);
    if (currentManager == null)
      {
        currentManager = new RepaintManager();
        currentRepaintManagers.put(threadGroup, currentManager);
      }
    return currentManager;
Tom Tromey committed
266 267 268
  }

  /**
269 270 271 272 273 274 275
   * Returns the <code>RepaintManager</code> for the current thread's
   * thread group. The default implementation ignores the
   * <code>component</code> parameter and returns the same repaint manager
   * for all components.
   *
   * This method is only here for backwards compatibility with older versions
   * of Swing and simply forwards to {@link #currentManager(Component)}.
Tom Tromey committed
276
   *
277
   * @param component a component to look up the manager of
Tom Tromey committed
278
   *
279 280
   * @return the current repaint manager for the calling thread's thread group
   *         and the specified component
Tom Tromey committed
281 282 283 284 285 286 287 288 289
   *
   * @see #setCurrentManager
   */
  public static RepaintManager currentManager(JComponent component)
  {
    return currentManager((Component)component);
  }

  /**
290
   * Sets the repaint manager for the calling thread's thread group.
Tom Tromey committed
291
   *
292 293
   * @param manager the repaint manager to set for the current thread's thread
   *        group
Tom Tromey committed
294
   *
295
   * @see #currentManager(Component)
Tom Tromey committed
296 297 298
   */
  public static void setCurrentManager(RepaintManager manager)
  {
299
    if (currentRepaintManagers == null)
300
      currentRepaintManagers = new WeakHashMap();
301 302 303

    ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
    currentRepaintManagers.put(threadGroup, manager);
Tom Tromey committed
304 305 306 307 308 309 310 311 312 313 314
  }

  /**
   * Add a component to the {@link #invalidComponents} vector. If the
   * {@link #repaintWorker} class is not active, insert it in the system
   * event queue.
   *
   * @param component The component to add
   *
   * @see #removeInvalidComponent
   */
315
  public void addInvalidComponent(JComponent component)
Tom Tromey committed
316
  {
317
    Component ancestor = component;
Tom Tromey committed
318 319 320 321 322 323 324 325 326 327 328 329 330 331

    while (ancestor != null
           && (! (ancestor instanceof JComponent)
               || ! ((JComponent) ancestor).isValidateRoot() ))
      ancestor = ancestor.getParent();

    if (ancestor != null
        && ancestor instanceof JComponent
        && ((JComponent) ancestor).isValidateRoot())
      component = (JComponent) ancestor;

    if (invalidComponents.contains(component))
      return;

332 333 334 335 336
    synchronized (invalidComponents)
      {
        invalidComponents.add(component);
      }

Tom Tromey committed
337 338 339 340 341 342 343 344 345 346 347 348 349 350
    if (! repaintWorker.isLive())
      {
        repaintWorker.setLive(true);
        SwingUtilities.invokeLater(repaintWorker);
      }
  }

  /**
   * Remove a component from the {@link #invalidComponents} vector.
   *
   * @param component The component to remove
   *
   * @see #addInvalidComponent
   */
351
  public void removeInvalidComponent(JComponent component)
Tom Tromey committed
352
  {
353 354 355 356
    synchronized (invalidComponents)
      {
        invalidComponents.remove(component);
      }
Tom Tromey committed
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
  }

  /**
   * Add a region to the set of dirty regions for a specified component.
   * This involves union'ing the new region with any existing dirty region
   * associated with the component. If the {@link #repaintWorker} class
   * is not active, insert it in the system event queue.
   *
   * @param component The component to add a dirty region for
   * @param x The left x coordinate of the new dirty region
   * @param y The top y coordinate of the new dirty region
   * @param w The width of the new dirty region
   * @param h The height of the new dirty region
   *
   * @see #addDirtyRegion
   * @see #getDirtyRegion
   * @see #isCompletelyDirty
   * @see #markCompletelyClean
   * @see #markCompletelyDirty
   */
377 378
  public void addDirtyRegion(JComponent component, int x, int y,
                             int w, int h)
Tom Tromey committed
379
  {
380
    if (w <= 0 || h <= 0 || !component.isShowing())
Tom Tromey committed
381
      return;
382 383 384
    
    Component parent = component.getParent();
    
385 386 387 388
    component.computeVisibleRect(rectCache);
    SwingUtilities.computeIntersection(x, y, w, h, rectCache);

    if (! rectCache.isEmpty())
Tom Tromey committed
389
      {
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
        if (dirtyComponents.containsKey(component))
          {
            SwingUtilities.computeUnion(rectCache.x, rectCache.y,
                                        rectCache.width, rectCache.height,
                                   (Rectangle) dirtyComponents.get(component));
          }
        else
          {
            synchronized (dirtyComponents)
              {
                dirtyComponents.put(component, rectCache.getBounds());
              }
          }

        if (! repaintWorker.isLive())
          {
            repaintWorker.setLive(true);
            SwingUtilities.invokeLater(repaintWorker);
          }
Tom Tromey committed
409 410
      }
  }
411 412

  /**
Tom Tromey committed
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
   * Get the dirty region associated with a component, or <code>null</code>
   * if the component has no dirty region.
   *
   * @param component The component to get the dirty region of
   *
   * @return The dirty region of the component
   *
   * @see #dirtyComponents
   * @see #addDirtyRegion
   * @see #isCompletelyDirty
   * @see #markCompletelyClean
   * @see #markCompletelyDirty
   */
  public Rectangle getDirtyRegion(JComponent component)
  {
428 429 430 431
    Rectangle dirty = (Rectangle) dirtyComponents.get(component);
    if (dirty == null)
      dirty = new Rectangle();
    return dirty;
Tom Tromey committed
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
  }
  
  /**
   * Mark a component as dirty over its entire bounds.
   *
   * @param component The component to mark as dirty
   *
   * @see #dirtyComponents
   * @see #addDirtyRegion
   * @see #getDirtyRegion
   * @see #isCompletelyDirty
   * @see #markCompletelyClean
   */
  public void markCompletelyDirty(JComponent component)
  {
    Rectangle r = component.getBounds();
448
    addDirtyRegion(component, 0, 0, r.width, r.height);
Tom Tromey committed
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
  }

  /**
   * Remove all dirty regions for a specified component
   *
   * @param component The component to mark as clean
   *
   * @see #dirtyComponents
   * @see #addDirtyRegion
   * @see #getDirtyRegion
   * @see #isCompletelyDirty
   * @see #markCompletelyDirty
   */
  public void markCompletelyClean(JComponent component)
  {
464
    synchronized (dirtyComponents)
465 466 467
      {
        dirtyComponents.remove(component);
      }
Tom Tromey committed
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
  }

  /**
   * Return <code>true</code> if the specified component is completely
   * contained within its dirty region, otherwise <code>false</code>
   *
   * @param component The component to check for complete dirtyness
   *
   * @return Whether the component is completely dirty
   *
   * @see #dirtyComponents
   * @see #addDirtyRegion
   * @see #getDirtyRegion
   * @see #isCompletelyDirty
   * @see #markCompletelyClean
   */
  public boolean isCompletelyDirty(JComponent component)
  {
486 487 488 489 490 491 492
    boolean retVal = false;
    if (dirtyComponents.containsKey(component))
      {
        Rectangle dirtyRegion = (Rectangle) dirtyComponents.get(component);
        retVal = dirtyRegion.equals(SwingUtilities.getLocalBounds(component));
      }
    return retVal;
Tom Tromey committed
493 494 495 496 497 498 499 500
  }

  /**
   * Validate all components which have been marked invalid in the {@link
   * #invalidComponents} vector.
   */
  public void validateInvalidComponents()
  {
501 502 503 504
    // We don't use an iterator here because that would fail when there are
    // components invalidated during the validation of others, which happens
    // quite frequently. Instead we synchronize the access a little more.
    while (invalidComponents.size() > 0)
Tom Tromey committed
505
      {
506 507 508 509 510 511
        Component comp;
        synchronized (invalidComponents)
          {
            comp = (Component) invalidComponents.remove(0);
          }
        // Validate the validate component.
Tom Tromey committed
512 513 514 515 516 517 518
        if (! (comp.isVisible() && comp.isShowing()))
          continue;
        comp.validate();
      }
  }

  /**
519 520
   * Repaint all regions of all components which have been marked dirty in the
   * {@link #dirtyComponents} table.
Tom Tromey committed
521
   */
522
  public void paintDirtyRegions()
Tom Tromey committed
523
  {
524 525 526 527
    // Short cicuit if there is nothing to paint.
    if (dirtyComponents.size() == 0)
      return;

528
    // Swap dirtyRegions with dirtyRegionsWork to avoid locking.
529
    synchronized (dirtyComponents)
Tom Tromey committed
530
      {
531 532 533
        HashMap swap = dirtyComponents;
        dirtyComponents = dirtyComponentsWork;
        dirtyComponentsWork = swap;
Tom Tromey committed
534
      }
535

536 537 538 539 540 541 542 543
    // Compile a set of repaint roots.
    HashSet repaintRoots = new HashSet();
    Set components = dirtyComponentsWork.keySet();
    for (Iterator i = components.iterator(); i.hasNext();)
      {
        JComponent dirty = (JComponent) i.next();
        compileRepaintRoots(dirtyComponentsWork, dirty, repaintRoots);
      }
544 545

    repaintUnderway = true;
546
    for (Iterator i = repaintRoots.iterator(); i.hasNext();)
547 548 549 550 551 552 553
      {
        JComponent comp = (JComponent) i.next();
        Rectangle damaged = (Rectangle) dirtyComponentsWork.remove(comp);
        if (damaged == null || damaged.isEmpty())
          continue;
        comp.paintImmediately(damaged);
      }
554
    dirtyComponentsWork.clear();
555 556
    repaintUnderway = false;
    commitRemainingBuffers();
Tom Tromey committed
557
  }
558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
  
  /**
   * Compiles a list of components that really get repainted. This is called
   * once for each component in the dirtyComponents HashMap, each time with
   * another <code>dirty</code> parameter. This searches up the component
   * hierarchy of <code>dirty</code> to find the highest parent that is also
   * marked dirty and merges the dirty regions.
   *
   * @param dirtyRegions the dirty regions 
   * @param dirty the component for which to find the repaint root
   * @param roots the list to which new repaint roots get appended
   */
  private void compileRepaintRoots(HashMap dirtyRegions, JComponent dirty,
                                   HashSet roots)
  {
    Component current = dirty;
    Component root = dirty;

    // Search the highest component that is also marked dirty.
    Component parent;
    while (true)
      {
        parent = current.getParent();
        if (parent == null || !(parent instanceof JComponent))
          break;

        current = parent;
        // We can skip to the next up when this parent is not dirty.
        if (dirtyRegions.containsKey(parent))
          {
            root = current;
          }
      }

    // Merge the rectangles of the root and the requested component if
    // the are different.
    if (root != dirty)
      {
        Rectangle dirtyRect = (Rectangle) dirtyRegions.get(dirty);
        dirtyRect = SwingUtilities.convertRectangle(dirty, dirtyRect, root);
        Rectangle rootRect = (Rectangle) dirtyRegions.get(root);
        SwingUtilities.computeUnion(dirtyRect.x, dirtyRect.y, dirtyRect.width,
                                    dirtyRect.height, rootRect);
      }

    // Adds the root to the roots set.
    roots.add(root);
  }
Tom Tromey committed
606 607 608 609 610 611 612 613 614 615 616 617 618 619 620

  /**
   * Get an offscreen buffer for painting a component's image. This image
   * may be smaller than the proposed dimensions, depending on the value of
   * the {@link #doubleBufferMaximumSize} property.
   *
   * @param component The component to return an offscreen buffer for
   * @param proposedWidth The proposed width of the offscreen buffer
   * @param proposedHeight The proposed height of the offscreen buffer
   *
   * @return A shared offscreen buffer for painting
   */
  public Image getOffscreenBuffer(Component component, int proposedWidth,
                                  int proposedHeight)
  {
621
    Component root = getRoot(component);
622 623 624 625 626 627 628 629 630 631 632 633 634 635
    Image buffer = (Image) offscreenBuffers.get(root);
    if (buffer == null 
        || buffer.getWidth(null) < proposedWidth 
        || buffer.getHeight(null) < proposedHeight)
      {
        int width = Math.max(proposedWidth, root.getWidth());
        width = Math.min(doubleBufferMaximumSize.width, width);
        int height = Math.max(proposedHeight, root.getHeight());
        height = Math.min(doubleBufferMaximumSize.height, height);
        buffer = component.createImage(width, height);
        offscreenBuffers.put(root, buffer);
      }
    return buffer;
  }
636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662
  
  /**
   * Gets the root of the component given. If a parent of the 
   * component is an instance of Applet, then the applet is 
   * returned. The applet is considered the root for painting.
   * Otherwise, the root Window is returned if it exists.
   * 
   * @param comp - The component to get the root for.
   * @return the parent root. An applet if it is a parent,
   * or the root window. If neither exist, null is returned.
   */
  private Component getRoot(Component comp)
  {
      Applet app = null;
      
      while (comp != null)
        {
          if (app == null && comp instanceof Window)
            return comp;
          else if (comp instanceof Applet)
            app = (Applet) comp;
          comp = comp.getParent();
        }
      
      return app;
  }
  
663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712
  /**
   * Blits the back buffer of the specified root component to the screen. If
   * the RepaintManager is currently working on a paint request, the commit
   * requests are queued up and committed at once when the paint request is
   * done (by {@link #commitRemainingBuffers}). This is package private because
   * it must get called by JComponent.
   *
   * @param root the component, either a Window or an Applet instance
   * @param area the area to paint on screen
   */
  void commitBuffer(Component root, Rectangle area)
  {
    // We synchronize on dirtyComponents here because that is what
    // paintDirtyRegions also synchronizes on while painting.
    synchronized (dirtyComponents)
      {
        // If the RepaintManager is not currently painting, then directly
        // blit the requested buffer on the screen.
        if (! repaintUnderway)
          {
            Graphics g = root.getGraphics();
            Image buffer = (Image) offscreenBuffers.get(root);
            Rectangle clip = g.getClipBounds();
            if (clip != null)
              area = SwingUtilities.computeIntersection(clip.x, clip.y,
                                                        clip.width, clip.height,
                                                        area);
            int dx1 = area.x;
            int dy1 = area.y;
            int dx2 = area.x + area.width;
            int dy2 = area.y + area.height;
            // Make sure we have a sane clip at this point.
            g.clipRect(area.x, area.y, area.width, area.height);

            // Make sure the coordinates are inside the buffer, everything else
            // might lead to problems.
            // TODO: This code should not really be necessary, however, in fact
            // we have two issues here:
            // 1. We shouldn't get repaint requests in areas outside the buffer
            //    region in the first place. This still happens for example
            //    when a component is inside a JViewport, and the component has
            //    a size that would reach beyond the window size.
            // 2. Graphics.drawImage() should not behave strange when trying
            //    to draw regions outside the image.
            int bufferWidth = buffer.getWidth(root);
            int bufferHeight = buffer.getHeight(root);
            dx1 = Math.min(bufferWidth, dx1);
            dy1 = Math.min(bufferHeight, dy1);
            dx2 = Math.min(bufferWidth, dx2);
            dy2 = Math.min(bufferHeight, dy2);
713
            g.drawImage(buffer, 0, 0, root);
714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
            g.dispose();
          }
        // Otherwise queue this request up, until all the RepaintManager work
        // is done.
        else
          {
            if (commitRequests.containsKey(root))
              SwingUtilities.computeUnion(area.x, area.y, area.width,
                                          area.height,
                                         (Rectangle) commitRequests.get(root));
            else
              commitRequests.put(root, area);
          }
      }
  }

  /**
   * Commits the queued up back buffers to screen all at once.
   */
  private void commitRemainingBuffers()
  {
    // We synchronize on dirtyComponents here because that is what
    // paintDirtyRegions also synchronizes on while painting.
    synchronized (dirtyComponents)
Tom Tromey committed
738
      {
739 740 741 742 743 744 745 746 747 748
        Set entrySet = commitRequests.entrySet();
        Iterator i = entrySet.iterator();
        while (i.hasNext())
          {
            Map.Entry entry = (Map.Entry) i.next();
            Component root = (Component) entry.getKey();
            Rectangle area = (Rectangle) entry.getValue();
            commitBuffer(root, area);
            i.remove();
          }
Tom Tromey committed
749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764
      }
  }

  /**
   * Creates and returns a volatile offscreen buffer for the specified
   * component that can be used as a double buffer. The returned image
   * is a {@link VolatileImage}. Its size will be <code>(proposedWidth,
   * proposedHeight)</code> except when the maximum double buffer size
   * has been set in this RepaintManager.
   *
   * @param comp the Component for which to create a volatile buffer
   * @param proposedWidth the proposed width of the buffer
   * @param proposedHeight the proposed height of the buffer
   *
   * @since 1.4
   *
Tom Tromey committed
765
   * @see VolatileImage
Tom Tromey committed
766 767 768 769
   */
  public Image getVolatileOffscreenBuffer(Component comp, int proposedWidth,
                                          int proposedHeight)
  {
770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785
    Component root = getRoot(comp);
    Image buffer = (Image) offscreenBuffers.get(root);
    if (buffer == null 
        || buffer.getWidth(null) < proposedWidth 
        || buffer.getHeight(null) < proposedHeight
        || !(buffer instanceof VolatileImage))
      {
        int width = Math.max(proposedWidth, root.getWidth());
        width = Math.min(doubleBufferMaximumSize.width, width);
        int height = Math.max(proposedHeight, root.getHeight());
        height = Math.min(doubleBufferMaximumSize.height, height);
        buffer = root.createVolatileImage(width, height);
        if (buffer != null)
          offscreenBuffers.put(root, buffer);
      }
    return buffer;
Tom Tromey committed
786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817
  }
  

  /**
   * Get the value of the {@link #doubleBufferMaximumSize} property.
   *
   * @return The current value of the property
   *
   * @see #setDoubleBufferMaximumSize
   */
  public Dimension getDoubleBufferMaximumSize()
  {
    return doubleBufferMaximumSize;
  }

  /**
   * Set the value of the {@link #doubleBufferMaximumSize} property.
   *
   * @param size The new value of the property
   *
   * @see #getDoubleBufferMaximumSize
   */
  public void setDoubleBufferMaximumSize(Dimension size)
  {
    doubleBufferMaximumSize = size;
  }

  /**
   * Set the value of the {@link #doubleBufferingEnabled} property.
   *
   * @param buffer The new value of the property
   *
Tom Tromey committed
818
   * @see #isDoubleBufferingEnabled
Tom Tromey committed
819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841
   */
  public void setDoubleBufferingEnabled(boolean buffer)
  {
    doubleBufferingEnabled = buffer;
  }

  /**
   * Get the value of the {@link #doubleBufferingEnabled} property.
   *
   * @return The current value of the property
   *
   * @see #setDoubleBufferingEnabled
   */
  public boolean isDoubleBufferingEnabled()
  {
    return doubleBufferingEnabled;
  }
  
  public String toString()
  {
    return "RepaintManager";
  }
}