mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			861 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			861 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Java
		
	
	
	
| /* RepaintManager.java --
 | |
|    Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
 | |
| 
 | |
| 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;
 | |
| 
 | |
| import gnu.classpath.SystemProperties;
 | |
| import gnu.java.awt.LowPriorityEvent;
 | |
| 
 | |
| import java.applet.Applet;
 | |
| import java.awt.Component;
 | |
| import java.awt.Dimension;
 | |
| import java.awt.EventQueue;
 | |
| import java.awt.Graphics;
 | |
| import java.awt.Image;
 | |
| import java.awt.Rectangle;
 | |
| import java.awt.Toolkit;
 | |
| import java.awt.Window;
 | |
| import java.awt.event.InvocationEvent;
 | |
| import java.awt.image.VolatileImage;
 | |
| import java.util.ArrayList;
 | |
| import java.util.HashMap;
 | |
| import java.util.HashSet;
 | |
| import java.util.Iterator;
 | |
| import java.util.Set;
 | |
| import java.util.WeakHashMap;
 | |
| 
 | |
| /**
 | |
|  * <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>
 | |
|  *
 | |
|  * <p>See <a
 | |
|  * href="http://java.sun.com/products/jfc/tsc/articles/painting/index.html">this
 | |
|  * document</a> for more details.</p>
 | |
|  * document</a> for more details.</p>
 | |
|  *
 | |
|  * @author Roman Kennke (kennke@aicas.com)
 | |
|  * @author Graydon Hoare (graydon@redhat.com)
 | |
|  * @author Audrius Meskauskas (audriusa@bioinformatics.org)
 | |
|  */
 | |
| public class RepaintManager
 | |
| {
 | |
|   /**
 | |
|    * An InvocationEvent subclass that implements LowPriorityEvent. This is used
 | |
|    * to defer the execution of RepaintManager requests as long as possible on
 | |
|    * the event queue. This way we make sure that all available input is
 | |
|    * processed before getting active with the RepaintManager. This allows
 | |
|    * for better optimization (more validate and repaint requests can be
 | |
|    * coalesced) and thus has a positive effect on performance for GUI
 | |
|    * applications under heavy load.
 | |
|    */
 | |
|   private static class RepaintWorkerEvent
 | |
|     extends InvocationEvent
 | |
|     implements LowPriorityEvent
 | |
|   {
 | |
| 
 | |
|     /**
 | |
|      * Creates a new RepaintManager event.
 | |
|      *
 | |
|      * @param source the source
 | |
|      * @param runnable the runnable to execute
 | |
|      */
 | |
|     public RepaintWorkerEvent(Object source, Runnable runnable,
 | |
|                               Object notifier, boolean catchEx)
 | |
|     {
 | |
|       super(source, runnable, notifier, catchEx);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * An application that I met implements its own event dispatching and
 | |
|      * calls dispatch() via reflection, and only checks declared methods,
 | |
|      * that is, it expects this method to be in the event's class, not
 | |
|      * in a superclass. So I put this in here... sigh.
 | |
|      */
 | |
|     public void dispatch()
 | |
|     {
 | |
|       super.dispatch();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * The current repaint managers, indexed by their ThreadGroups.
 | |
|    */
 | |
|   static WeakHashMap currentRepaintManagers;
 | |
| 
 | |
|   /**
 | |
|    * A rectangle object to be reused in damaged regions calculation.
 | |
|    */
 | |
|   private static Rectangle rectCache = new Rectangle();
 | |
| 
 | |
|   /**
 | |
|    * <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
 | |
|    * not it inserts one using {@link SwingUtilities#invokeLater}.</p>
 | |
|    *
 | |
|    * <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>
 | |
|    */
 | |
|   private class RepaintWorker
 | |
|     implements Runnable
 | |
|   {
 | |
| 
 | |
|     boolean live;
 | |
| 
 | |
|     public RepaintWorker()
 | |
|     {
 | |
|       live = false;
 | |
|     }
 | |
| 
 | |
|     public synchronized void setLive(boolean b)
 | |
|     {
 | |
|       live = b;
 | |
|     }
 | |
| 
 | |
|     public synchronized boolean isLive()
 | |
|     {
 | |
|       return live;
 | |
|     }
 | |
| 
 | |
|     public void run()
 | |
|     {
 | |
|       try
 | |
|         {
 | |
|           ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
 | |
|           RepaintManager rm =
 | |
|             (RepaintManager) currentRepaintManagers.get(threadGroup);
 | |
|           rm.validateInvalidComponents();
 | |
|           rm.paintDirtyRegions();
 | |
|         }
 | |
|       finally
 | |
|         {
 | |
|           setLive(false);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * 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.
 | |
|    *
 | |
|    * This is package private to avoid a synthetic accessor method in inner
 | |
|    * class.
 | |
|    *
 | |
|    * @see #addDirtyRegion
 | |
|    * @see #getDirtyRegion
 | |
|    * @see #isCompletelyDirty
 | |
|    * @see #markCompletelyClean
 | |
|    * @see #markCompletelyDirty
 | |
|    */
 | |
|   private HashMap dirtyComponents;
 | |
| 
 | |
|   /**
 | |
|    * The dirtyComponents which is used in paintDiryRegions to avoid unnecessary
 | |
|    * locking.
 | |
|    */
 | |
|   private HashMap dirtyComponentsWork;
 | |
| 
 | |
|   /**
 | |
|    * 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
 | |
|    */
 | |
|   private RepaintWorker repaintWorker;
 | |
| 
 | |
|   /**
 | |
|    * 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
 | |
|    */
 | |
|   private ArrayList invalidComponents;
 | |
| 
 | |
|   /**
 | |
|    * 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.
 | |
|    *
 | |
|    * @see #isDoubleBufferingEnabled
 | |
|    * @see #setDoubleBufferingEnabled
 | |
|    */
 | |
|   private boolean doubleBufferingEnabled;
 | |
| 
 | |
|   /**
 | |
|    * The offscreen buffers. This map holds one offscreen buffer per
 | |
|    * Window/Applet and releases them as soon as the Window/Applet gets garbage
 | |
|    * collected.
 | |
|    */
 | |
|   private WeakHashMap offscreenBuffers;
 | |
| 
 | |
|   /**
 | |
|    * The maximum width and height to allocate as a double buffer. Requests
 | |
|    * beyond this size are ignored.
 | |
|    *
 | |
|    * @see #paintDirtyRegions
 | |
|    * @see #getDoubleBufferMaximumSize
 | |
|    * @see #setDoubleBufferMaximumSize
 | |
|    */
 | |
|   private Dimension doubleBufferMaximumSize;
 | |
| 
 | |
| 
 | |
|   /**
 | |
|    * Create a new RepaintManager object.
 | |
|    */
 | |
|   public RepaintManager()
 | |
|   {
 | |
|     dirtyComponents = new HashMap();
 | |
|     dirtyComponentsWork = new HashMap();
 | |
|     invalidComponents = new ArrayList();
 | |
|     repaintWorker = new RepaintWorker();
 | |
|     doubleBufferMaximumSize = new Dimension(2000,2000);
 | |
|     doubleBufferingEnabled =
 | |
|       SystemProperties.getProperty("gnu.swing.doublebuffering", "true")
 | |
|                       .equals("true");
 | |
|     offscreenBuffers = new WeakHashMap();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * 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.
 | |
|    *
 | |
|    * @param component a component to look up the manager of
 | |
|    *
 | |
|    * @return the current repaint manager for the calling thread's thread group
 | |
|    *         and the specified component
 | |
|    *
 | |
|    * @see #setCurrentManager
 | |
|    */
 | |
|   public static RepaintManager currentManager(Component component)
 | |
|   {
 | |
|     if (currentRepaintManagers == null)
 | |
|       currentRepaintManagers = new WeakHashMap();
 | |
|     ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
 | |
|     RepaintManager currentManager =
 | |
|       (RepaintManager) currentRepaintManagers.get(threadGroup);
 | |
|     if (currentManager == null)
 | |
|       {
 | |
|         currentManager = new RepaintManager();
 | |
|         currentRepaintManagers.put(threadGroup, currentManager);
 | |
|       }
 | |
|     return currentManager;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * 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)}.
 | |
|    *
 | |
|    * @param component a component to look up the manager of
 | |
|    *
 | |
|    * @return the current repaint manager for the calling thread's thread group
 | |
|    *         and the specified component
 | |
|    *
 | |
|    * @see #setCurrentManager
 | |
|    */
 | |
|   public static RepaintManager currentManager(JComponent component)
 | |
|   {
 | |
|     return currentManager((Component)component);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Sets the repaint manager for the calling thread's thread group.
 | |
|    *
 | |
|    * @param manager the repaint manager to set for the current thread's thread
 | |
|    *        group
 | |
|    *
 | |
|    * @see #currentManager(Component)
 | |
|    */
 | |
|   public static void setCurrentManager(RepaintManager manager)
 | |
|   {
 | |
|     if (currentRepaintManagers == null)
 | |
|       currentRepaintManagers = new WeakHashMap();
 | |
| 
 | |
|     ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
 | |
|     currentRepaintManagers.put(threadGroup, manager);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * 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
 | |
|    */
 | |
|   public void addInvalidComponent(JComponent component)
 | |
|   {
 | |
|     Component validateRoot = null;
 | |
|     Component c = component;
 | |
|     while (c != null)
 | |
|       {
 | |
|         // Special cases we don't bother validating are when the invalidated
 | |
|         // component (or any of it's ancestors) is inside a CellRendererPane
 | |
|         // or if it doesn't have a peer yet (== not displayable).
 | |
|         if (c instanceof CellRendererPane || ! c.isDisplayable())
 | |
|           return;
 | |
|         if (c instanceof JComponent && ((JComponent) c).isValidateRoot())
 | |
|           {
 | |
|             validateRoot = c;
 | |
|             break;
 | |
|           }
 | |
| 
 | |
|         c = c.getParent();
 | |
|       }
 | |
| 
 | |
|     // If we didn't find a validate root, then we don't validate.
 | |
|     if (validateRoot == null)
 | |
|       return;
 | |
| 
 | |
|     // Make sure the validate root and all of it's ancestors are visible.
 | |
|     c = validateRoot;
 | |
|     while (c != null)
 | |
|       {
 | |
|         if (! c.isVisible() || ! c.isDisplayable())
 | |
|           return;
 | |
|         c = c.getParent();
 | |
|       }
 | |
| 
 | |
|     if (invalidComponents.contains(validateRoot))
 | |
|       return;
 | |
| 
 | |
|     //synchronized (invalidComponents)
 | |
|     //  {
 | |
|         invalidComponents.add(validateRoot);
 | |
|     //  }
 | |
| 
 | |
|     if (! repaintWorker.isLive())
 | |
|       {
 | |
|         repaintWorker.setLive(true);
 | |
|         invokeLater(repaintWorker);
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Remove a component from the {@link #invalidComponents} vector.
 | |
|    *
 | |
|    * @param component The component to remove
 | |
|    *
 | |
|    * @see #addInvalidComponent
 | |
|    */
 | |
|   public void removeInvalidComponent(JComponent component)
 | |
|   {
 | |
|     synchronized (invalidComponents)
 | |
|       {
 | |
|         invalidComponents.remove(component);
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * 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
 | |
|    */
 | |
|   public void addDirtyRegion(JComponent component, int x, int y,
 | |
|                              int w, int h)
 | |
|   {
 | |
|     if (w <= 0 || h <= 0 || !component.isShowing())
 | |
|       return;
 | |
|     component.computeVisibleRect(rectCache);
 | |
|     SwingUtilities.computeIntersection(x, y, w, h, rectCache);
 | |
| 
 | |
|     if (! rectCache.isEmpty())
 | |
|       {
 | |
|         synchronized (dirtyComponents)
 | |
|           {
 | |
|             Rectangle dirtyRect = (Rectangle)dirtyComponents.get(component);
 | |
|             if (dirtyRect != null)
 | |
|               {
 | |
|                 SwingUtilities.computeUnion(rectCache.x, rectCache.y,
 | |
|                                             rectCache.width, rectCache.height,
 | |
|                                             dirtyRect);
 | |
|               }
 | |
|             else
 | |
|               {
 | |
|                 dirtyComponents.put(component, rectCache.getBounds());
 | |
|               }
 | |
|           }
 | |
| 
 | |
|         if (! repaintWorker.isLive())
 | |
|           {
 | |
|             repaintWorker.setLive(true);
 | |
|             invokeLater(repaintWorker);
 | |
|           }
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * 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)
 | |
|   {
 | |
|     Rectangle dirty = (Rectangle) dirtyComponents.get(component);
 | |
|     if (dirty == null)
 | |
|       dirty = new Rectangle();
 | |
|     return dirty;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * 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)
 | |
|   {
 | |
|     addDirtyRegion(component, 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * 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)
 | |
|   {
 | |
|     synchronized (dirtyComponents)
 | |
|       {
 | |
|         dirtyComponents.remove(component);
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * 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)
 | |
|   {
 | |
|     boolean dirty = false;
 | |
|     Rectangle r = getDirtyRegion(component);
 | |
|     if(r.width == Integer.MAX_VALUE && r.height == Integer.MAX_VALUE)
 | |
|       dirty = true;
 | |
|     return dirty;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Validate all components which have been marked invalid in the {@link
 | |
|    * #invalidComponents} vector.
 | |
|    */
 | |
|   public void validateInvalidComponents()
 | |
|   {
 | |
|     // 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)
 | |
|       {
 | |
|         Component comp;
 | |
|         synchronized (invalidComponents)
 | |
|           {
 | |
|             comp = (Component) invalidComponents.remove(0);
 | |
|           }
 | |
|         // Validate the validate component.
 | |
|         if (! (comp.isVisible() && comp.isShowing()))
 | |
|           continue;
 | |
|         comp.validate();
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Repaint all regions of all components which have been marked dirty in the
 | |
|    * {@link #dirtyComponents} table.
 | |
|    */
 | |
|   public void paintDirtyRegions()
 | |
|   {
 | |
|     // Short circuit if there is nothing to paint.
 | |
|     if (dirtyComponents.size() == 0)
 | |
|       return;
 | |
| 
 | |
|     // Swap dirtyRegions with dirtyRegionsWork to avoid locking.
 | |
|     synchronized (dirtyComponents)
 | |
|       {
 | |
|         HashMap swap = dirtyComponents;
 | |
|         dirtyComponents = dirtyComponentsWork;
 | |
|         dirtyComponentsWork = swap;
 | |
|       }
 | |
| 
 | |
|     // 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);
 | |
|       }
 | |
| 
 | |
|     for (Iterator i = repaintRoots.iterator(); i.hasNext();)
 | |
|       {
 | |
|         JComponent comp = (JComponent) i.next();
 | |
|         Rectangle damaged = (Rectangle) dirtyComponentsWork.remove(comp);
 | |
|         if (damaged == null || damaged.isEmpty())
 | |
|           continue;
 | |
|         comp.paintImmediately(damaged);
 | |
|       }
 | |
|     dirtyComponentsWork.clear();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Compiles a list of components that really get repainted. This is called
 | |
|    * once for each component in the dirtyRegions 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;
 | |
| 
 | |
|     // This will contain the dirty region in the root coordinate system,
 | |
|     // possibly clipped by ancestor's bounds.
 | |
|     Rectangle originalDirtyRect = (Rectangle) dirtyRegions.get(dirty);
 | |
|     rectCache.setBounds(originalDirtyRect);
 | |
| 
 | |
|     // The bounds of the current component.
 | |
|     int x = dirty.getX();
 | |
|     int y = dirty.getY();
 | |
|     int w = dirty.getWidth();
 | |
|     int h = dirty.getHeight();
 | |
| 
 | |
|     // Do nothing if dirty region is clipped away by the component's bounds.
 | |
|     rectCache = SwingUtilities.computeIntersection(0, 0, w, h, rectCache);
 | |
|     if (rectCache.isEmpty())
 | |
|       return;
 | |
| 
 | |
|     // The cumulated offsets.
 | |
|     int dx = 0;
 | |
|     int dy = 0;
 | |
|     // The actual offset for the found root.
 | |
|     int rootDx = 0;
 | |
|     int rootDy = 0;
 | |
| 
 | |
|     // 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;
 | |
|         // Update the offset.
 | |
|         dx += x;
 | |
|         dy += y;
 | |
|         rectCache.x += x;
 | |
|         rectCache.y += y;
 | |
| 
 | |
|         x = current.getX();
 | |
|         y = current.getY();
 | |
|         w = current.getWidth();
 | |
|         h = current.getHeight();
 | |
|         rectCache = SwingUtilities.computeIntersection(0, 0, w, h, rectCache);
 | |
| 
 | |
|         // Don't paint if the dirty regions is clipped away by any of
 | |
|         // its ancestors.
 | |
|         if (rectCache.isEmpty())
 | |
|           return;
 | |
| 
 | |
|         // We can skip to the next up when this parent is not dirty.
 | |
|         if (dirtyRegions.containsKey(parent))
 | |
|           {
 | |
|             root = current;
 | |
|             rootDx = dx;
 | |
|             rootDy = dy;
 | |
|           }
 | |
|       }
 | |
| 
 | |
|     // Merge the rectangles of the root and the requested component if
 | |
|     // the are different.
 | |
|     if (root != dirty)
 | |
|       {
 | |
|         rectCache.x += rootDx - dx;
 | |
|         rectCache.y += rootDy - dy;
 | |
|         Rectangle dirtyRect = (Rectangle) dirtyRegions.get(root);
 | |
|         SwingUtilities.computeUnion(rectCache.x, rectCache.y, rectCache.width,
 | |
|                                     rectCache.height, dirtyRect);
 | |
|       }
 | |
| 
 | |
|     // Adds the root to the roots set.
 | |
|     if (! roots.contains(root))
 | |
|       roots.add(root);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * 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)
 | |
|   {
 | |
|     Component root = SwingUtilities.getWindowAncestor(component);
 | |
|     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;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Blits the back buffer of the specified root component to the screen.
 | |
|    * This is package private because it must get called by JComponent.
 | |
|    *
 | |
|    * @param comp the component to be painted
 | |
|    * @param x the area to paint on screen, in comp coordinates
 | |
|    * @param y the area to paint on screen, in comp coordinates
 | |
|    * @param w the area to paint on screen, in comp coordinates
 | |
|    * @param h the area to paint on screen, in comp coordinates
 | |
|    */
 | |
|   void commitBuffer(Component comp, int x, int y, int w, int h)
 | |
|   {
 | |
|     Component root = comp;
 | |
|     while (root != null
 | |
|            && ! (root instanceof Window || root instanceof Applet))
 | |
|       {
 | |
|         x += root.getX();
 | |
|         y += root.getY();
 | |
|         root = root.getParent();
 | |
|       }
 | |
| 
 | |
|     if (root != null)
 | |
|       {
 | |
|         Graphics g = root.getGraphics();
 | |
|         Image buffer = (Image) offscreenBuffers.get(root);
 | |
|         if (buffer != null)
 | |
|           {
 | |
|             // Make sure we have a sane clip at this point.
 | |
|             g.clipRect(x, y, w, h);
 | |
|             g.drawImage(buffer, 0, 0, root);
 | |
|             g.dispose();
 | |
|           }
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * 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
 | |
|    *
 | |
|    * @see VolatileImage
 | |
|    */
 | |
|   public Image getVolatileOffscreenBuffer(Component comp, int proposedWidth,
 | |
|                                           int proposedHeight)
 | |
|   {
 | |
|     Component root = SwingUtilities.getWindowAncestor(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;
 | |
|   }
 | |
| 
 | |
| 
 | |
|   /**
 | |
|    * 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
 | |
|    *
 | |
|    * @see #isDoubleBufferingEnabled
 | |
|    */
 | |
|   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";
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Sends an RepaintManagerEvent to the event queue with the specified
 | |
|    * runnable. This is similar to SwingUtilities.invokeLater(), only that the
 | |
|    * event is a low priority event in order to defer the execution a little
 | |
|    * more.
 | |
|    */
 | |
|   private void invokeLater(Runnable runnable)
 | |
|   {
 | |
|     Toolkit tk = Toolkit.getDefaultToolkit();
 | |
|     EventQueue evQueue = tk.getSystemEventQueue();
 | |
|     InvocationEvent ev = new RepaintWorkerEvent(evQueue, runnable, null, false);
 | |
|     evQueue.postEvent(ev);
 | |
|   }
 | |
| }
 |