| /* RepaintManager.java -- |
| Copyright (C) 2002, 2004 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., 59 Temple Place, Suite 330, Boston, MA |
| 02111-1307 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 java.awt.Component; |
| import java.awt.Dimension; |
| import java.awt.Image; |
| import java.awt.Rectangle; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Vector; |
| |
| /** |
| * <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>In general, painting is very confusing in swing. see <a |
| * href="http://java.sun.com/products/jfc/tsc/articles/painting/index.html">this |
| * document</a> for more details.</p> |
| * |
| * @author Graydon Hoare (graydon@redhat.com) |
| */ |
| public class RepaintManager |
| { |
| |
| /** |
| * <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> |
| */ |
| |
| protected 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() |
| { |
| RepaintManager rm = RepaintManager.globalManager; |
| setLive(false); |
| rm.validateInvalidComponents(); |
| rm.paintDirtyRegions(); |
| } |
| } |
| |
| |
| /** |
| * 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. |
| * |
| * @see #addDirtyRegion |
| * @see #getDirtyRegion |
| * @see #isCompletelyDirty |
| * @see #markCompletelyClean |
| * @see #markCompletelyDirty |
| */ |
| Hashtable dirtyComponents; |
| |
| /** |
| * 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 |
| */ |
| 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 |
| */ |
| Vector 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 #getDoubleBufferingEnabled |
| * @see #setDoubleBufferingEnabled |
| */ |
| boolean doubleBufferingEnabled; |
| |
| /** |
| * The current offscreen buffer. This is reused for all requests for |
| * offscreen drawing buffers. It grows as necessary, up to {@link |
| * #doubleBufferMaximumSize}, but there is only one shared instance. |
| * |
| * @see #getOffscreenBuffer |
| * @see #doubleBufferMaximumSize |
| */ |
| Image doubleBuffer; |
| |
| /** |
| * The maximum width and height to allocate as a double buffer. Requests |
| * beyond this size are ignored. |
| * |
| * @see #paintDirtyRegions |
| * @see #getDoubleBufferMaximumSize |
| * @see #setDoubleBufferMaximumSize |
| */ |
| Dimension doubleBufferMaximumSize; |
| |
| |
| /** |
| * The global, shared RepaintManager instance. This is reused for all |
| * components in all windows. |
| * |
| * @see #currentManager |
| * @see #setCurrentManager |
| */ |
| private static RepaintManager globalManager; |
| |
| /** |
| * Create a new RepaintManager object. |
| */ |
| public RepaintManager() |
| { |
| dirtyComponents = new Hashtable(); |
| invalidComponents = new Vector(); |
| repaintWorker = new RepaintWorker(); |
| doubleBufferMaximumSize = new Dimension(2000,2000); |
| doubleBufferingEnabled = true; |
| } |
| |
| /** |
| * Get the value of the shared {@link #globalManager} instance, possibly |
| * returning a special manager associated with the specified |
| * component. The default implementaiton ignores the component parameter. |
| * |
| * @param component A component to look up the manager of |
| * |
| * @return The current repaint manager |
| * |
| * @see #setCurrentManager |
| */ |
| public static RepaintManager currentManager(Component component) |
| { |
| if (globalManager == null) |
| globalManager = new RepaintManager(); |
| return globalManager; |
| } |
| |
| /** |
| * Get the value of the shared {@link #globalManager} instance, possibly |
| * returning a special manager associated with the specified |
| * component. The default implementaiton ignores the component parameter. |
| * |
| * @param component A component to look up the manager of |
| * |
| * @return The current repaint manager |
| * |
| * @see #setCurrentManager |
| */ |
| public static RepaintManager currentManager(JComponent component) |
| { |
| return currentManager((Component)component); |
| } |
| |
| /** |
| * Set the value of the shared {@link #globalManager} instance. |
| * |
| * @param manager The new value of the shared instance |
| * |
| * @see #currentManager |
| */ |
| public static void setCurrentManager(RepaintManager manager) |
| { |
| globalManager = 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 synchronized void addInvalidComponent(JComponent component) |
| { |
| Component ancestor = component.getParent(); |
| |
| 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; |
| |
| invalidComponents.add(component); |
| |
| 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 |
| */ |
| public synchronized void removeInvalidComponent(JComponent component) |
| { |
| invalidComponents.removeElement(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 synchronized void addDirtyRegion(JComponent component, int x, int y, |
| int w, int h) |
| { |
| if (w == 0 || h == 0) |
| return; |
| |
| Rectangle r = new Rectangle(x, y, w, h); |
| if (dirtyComponents.containsKey(component)) |
| r = r.union((Rectangle)dirtyComponents.get(component)); |
| dirtyComponents.put(component, r); |
| if (! repaintWorker.isLive()) |
| { |
| repaintWorker.setLive(true); |
| SwingUtilities.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) |
| { |
| return (Rectangle) dirtyComponents.get(component); |
| } |
| |
| /** |
| * 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(); |
| addDirtyRegion(component, r.x, r.y, r.width, r.height); |
| } |
| |
| /** |
| * 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) |
| { |
| 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) |
| { |
| Rectangle dirty = (Rectangle) dirtyComponents.get(component); |
| if (dirty == null) |
| return false; |
| Rectangle r = component.getBounds(); |
| if (r == null) |
| return true; |
| return dirty.contains(r); |
| } |
| |
| /** |
| * Validate all components which have been marked invalid in the {@link |
| * #invalidComponents} vector. |
| */ |
| public void validateInvalidComponents() |
| { |
| for (Enumeration e = invalidComponents.elements(); e.hasMoreElements(); ) |
| { |
| JComponent comp = (JComponent) e.nextElement(); |
| if (! (comp.isVisible() && comp.isShowing())) |
| continue; |
| comp.validate(); |
| } |
| invalidComponents.clear(); |
| } |
| |
| /** |
| * Repaint all regions of all components which have been marked dirty in |
| * the {@link #dirtyComponents} table. |
| */ |
| public void paintDirtyRegions() |
| { |
| // step 1: pull out roots and calculate spanning damage |
| |
| HashMap roots = new HashMap(); |
| for (Enumeration e = dirtyComponents.keys(); e.hasMoreElements(); ) |
| { |
| JComponent comp = (JComponent) e.nextElement(); |
| if (! (comp.isVisible() && comp.isShowing())) |
| continue; |
| Rectangle damaged = getDirtyRegion(comp); |
| if (damaged.width == 0 || damaged.height == 0) |
| continue; |
| JRootPane root = comp.getRootPane(); |
| // If the component has no root, no repainting will occur. |
| if (root == null) |
| continue; |
| Rectangle rootDamage = SwingUtilities.convertRectangle(comp, damaged, root); |
| if (! roots.containsKey(root)) |
| { |
| roots.put(root, rootDamage); |
| } |
| else |
| { |
| roots.put(root, ((Rectangle)roots.get(root)).union(rootDamage)); |
| } |
| } |
| dirtyComponents.clear(); |
| |
| // step 2: paint those roots |
| Iterator i = roots.entrySet().iterator(); |
| while(i.hasNext()) |
| { |
| Map.Entry ent = (Map.Entry) i.next(); |
| JRootPane root = (JRootPane) ent.getKey(); |
| Rectangle rect = (Rectangle) ent.getValue(); |
| root.paintImmediately(rect); |
| } |
| } |
| |
| /** |
| * 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 |
| * |
| * @see #doubleBuffer |
| */ |
| public Image getOffscreenBuffer(Component component, int proposedWidth, |
| int proposedHeight) |
| { |
| if (doubleBuffer == null |
| || (((doubleBuffer.getWidth(null) < proposedWidth) |
| || (doubleBuffer.getHeight(null) < proposedHeight)) |
| && (proposedWidth < doubleBufferMaximumSize.width) |
| && (proposedHeight < doubleBufferMaximumSize.height))) |
| { |
| doubleBuffer = component.createImage(proposedWidth, proposedHeight); |
| } |
| return doubleBuffer; |
| } |
| |
| /** |
| * 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 #getDoubleBufferingEnabled |
| */ |
| 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"; |
| } |
| } |