From 554fd8c5195424bdbcabf5de30fdc183aba391bd Mon Sep 17 00:00:00 2001
From: upstream source tree 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. See this
+ * document for more details.
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}.
+ * + *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.
+ */ + 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 theRepaintManager
for the current thread's
+ * thread group. The default implementation ignores the
+ * component
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 RepaintManager
for the current thread's
+ * thread group. The default implementation ignores the
+ * component
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 null
+ * 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 true
if the specified component is completely
+ * contained within its dirty region, otherwise false
+ *
+ * @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 dirty
parameter. This searches up the component
+ * hierarchy of dirty
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 (proposedWidth,
+ * proposedHeight)
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);
+ }
+}
--
cgit v1.2.3