2015-05-05 Yvan Roux <yvan.roux@linaro.org>
[official-gcc.git] / libjava / classpath / javax / swing / RepaintManager.java
blob23c05a2627822e443fa8261301216ba00c03e46a
1 /* RepaintManager.java --
2 Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package javax.swing;
41 import gnu.classpath.SystemProperties;
42 import gnu.java.awt.LowPriorityEvent;
44 import java.applet.Applet;
45 import java.awt.Component;
46 import java.awt.Dimension;
47 import java.awt.EventQueue;
48 import java.awt.Graphics;
49 import java.awt.Image;
50 import java.awt.Rectangle;
51 import java.awt.Toolkit;
52 import java.awt.Window;
53 import java.awt.event.InvocationEvent;
54 import java.awt.image.VolatileImage;
55 import java.util.ArrayList;
56 import java.util.HashMap;
57 import java.util.HashSet;
58 import java.util.Iterator;
59 import java.util.Set;
60 import java.util.WeakHashMap;
62 /**
63 * <p>The repaint manager holds a set of dirty regions, invalid components,
64 * and a double buffer surface. The dirty regions and invalid components
65 * are used to coalesce multiple revalidate() and repaint() calls in the
66 * component tree into larger groups to be refreshed "all at once"; the
67 * double buffer surface is used by root components to paint
68 * themselves.</p>
70 * <p>See <a
71 * href="http://java.sun.com/products/jfc/tsc/articles/painting/index.html">this
72 * document</a> for more details.</p>
73 * document</a> for more details.</p>
75 * @author Roman Kennke (kennke@aicas.com)
76 * @author Graydon Hoare (graydon@redhat.com)
77 * @author Audrius Meskauskas (audriusa@bioinformatics.org)
79 public class RepaintManager
81 /**
82 * An InvocationEvent subclass that implements LowPriorityEvent. This is used
83 * to defer the execution of RepaintManager requests as long as possible on
84 * the event queue. This way we make sure that all available input is
85 * processed before getting active with the RepaintManager. This allows
86 * for better optimization (more validate and repaint requests can be
87 * coalesced) and thus has a positive effect on performance for GUI
88 * applications under heavy load.
90 private static class RepaintWorkerEvent
91 extends InvocationEvent
92 implements LowPriorityEvent
95 /**
96 * Creates a new RepaintManager event.
98 * @param source the source
99 * @param runnable the runnable to execute
101 public RepaintWorkerEvent(Object source, Runnable runnable,
102 Object notifier, boolean catchEx)
104 super(source, runnable, notifier, catchEx);
108 * An application that I met implements its own event dispatching and
109 * calls dispatch() via reflection, and only checks declared methods,
110 * that is, it expects this method to be in the event's class, not
111 * in a superclass. So I put this in here... sigh.
113 public void dispatch()
115 super.dispatch();
120 * The current repaint managers, indexed by their ThreadGroups.
122 static WeakHashMap currentRepaintManagers;
125 * A rectangle object to be reused in damaged regions calculation.
127 private static Rectangle rectCache = new Rectangle();
130 * <p>A helper class which is placed into the system event queue at
131 * various times in order to facilitate repainting and layout. There is
132 * typically only one of these objects active at any time. When the
133 * {@link RepaintManager} is told to queue a repaint, it checks to see if
134 * a {@link RepaintWorker} is "live" in the system event queue, and if
135 * not it inserts one using {@link SwingUtilities#invokeLater}.</p>
137 * <p>When the {@link RepaintWorker} comes to the head of the system
138 * event queue, its {@link RepaintWorker#run} method is executed by the
139 * swing paint thread, which revalidates all invalid components and
140 * repaints any damage in the swing scene.</p>
142 private class RepaintWorker
143 implements Runnable
146 boolean live;
148 public RepaintWorker()
150 live = false;
153 public synchronized void setLive(boolean b)
155 live = b;
158 public synchronized boolean isLive()
160 return live;
163 public void run()
167 ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
168 RepaintManager rm =
169 (RepaintManager) currentRepaintManagers.get(threadGroup);
170 rm.validateInvalidComponents();
171 rm.paintDirtyRegions();
173 finally
175 setLive(false);
182 * A table storing the dirty regions of components. The keys of this
183 * table are components, the values are rectangles. Each component maps
184 * to exactly one rectangle. When more regions are marked as dirty on a
185 * component, they are union'ed with the existing rectangle.
187 * This is package private to avoid a synthetic accessor method in inner
188 * class.
190 * @see #addDirtyRegion
191 * @see #getDirtyRegion
192 * @see #isCompletelyDirty
193 * @see #markCompletelyClean
194 * @see #markCompletelyDirty
196 private HashMap dirtyComponents;
199 * The dirtyComponents which is used in paintDiryRegions to avoid unnecessary
200 * locking.
202 private HashMap dirtyComponentsWork;
205 * A single, shared instance of the helper class. Any methods which mark
206 * components as invalid or dirty eventually activate this instance. It
207 * is added to the event queue if it is not already active, otherwise
208 * reused.
210 * @see #addDirtyRegion
211 * @see #addInvalidComponent
213 private RepaintWorker repaintWorker;
216 * The set of components which need revalidation, in the "layout" sense.
217 * There is no additional information about "what kind of layout" they
218 * need (as there is with dirty regions), so it is just a vector rather
219 * than a table.
221 * @see #addInvalidComponent
222 * @see #removeInvalidComponent
223 * @see #validateInvalidComponents
225 private ArrayList invalidComponents;
228 * Whether or not double buffering is enabled on this repaint
229 * manager. This is merely a hint to clients; the RepaintManager will
230 * always return an offscreen buffer when one is requested.
232 * @see #isDoubleBufferingEnabled
233 * @see #setDoubleBufferingEnabled
235 private boolean doubleBufferingEnabled;
238 * The offscreen buffers. This map holds one offscreen buffer per
239 * Window/Applet and releases them as soon as the Window/Applet gets garbage
240 * collected.
242 private WeakHashMap offscreenBuffers;
245 * The maximum width and height to allocate as a double buffer. Requests
246 * beyond this size are ignored.
248 * @see #paintDirtyRegions
249 * @see #getDoubleBufferMaximumSize
250 * @see #setDoubleBufferMaximumSize
252 private Dimension doubleBufferMaximumSize;
256 * Create a new RepaintManager object.
258 public RepaintManager()
260 dirtyComponents = new HashMap();
261 dirtyComponentsWork = new HashMap();
262 invalidComponents = new ArrayList();
263 repaintWorker = new RepaintWorker();
264 doubleBufferMaximumSize = new Dimension(2000,2000);
265 doubleBufferingEnabled =
266 SystemProperties.getProperty("gnu.swing.doublebuffering", "true")
267 .equals("true");
268 offscreenBuffers = new WeakHashMap();
272 * Returns the <code>RepaintManager</code> for the current thread's
273 * thread group. The default implementation ignores the
274 * <code>component</code> parameter and returns the same repaint manager
275 * for all components.
277 * @param component a component to look up the manager of
279 * @return the current repaint manager for the calling thread's thread group
280 * and the specified component
282 * @see #setCurrentManager
284 public static RepaintManager currentManager(Component component)
286 if (currentRepaintManagers == null)
287 currentRepaintManagers = new WeakHashMap();
288 ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
289 RepaintManager currentManager =
290 (RepaintManager) currentRepaintManagers.get(threadGroup);
291 if (currentManager == null)
293 currentManager = new RepaintManager();
294 currentRepaintManagers.put(threadGroup, currentManager);
296 return currentManager;
300 * Returns the <code>RepaintManager</code> for the current thread's
301 * thread group. The default implementation ignores the
302 * <code>component</code> parameter and returns the same repaint manager
303 * for all components.
305 * This method is only here for backwards compatibility with older versions
306 * of Swing and simply forwards to {@link #currentManager(Component)}.
308 * @param component a component to look up the manager of
310 * @return the current repaint manager for the calling thread's thread group
311 * and the specified component
313 * @see #setCurrentManager
315 public static RepaintManager currentManager(JComponent component)
317 return currentManager((Component)component);
321 * Sets the repaint manager for the calling thread's thread group.
323 * @param manager the repaint manager to set for the current thread's thread
324 * group
326 * @see #currentManager(Component)
328 public static void setCurrentManager(RepaintManager manager)
330 if (currentRepaintManagers == null)
331 currentRepaintManagers = new WeakHashMap();
333 ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
334 currentRepaintManagers.put(threadGroup, manager);
338 * Add a component to the {@link #invalidComponents} vector. If the
339 * {@link #repaintWorker} class is not active, insert it in the system
340 * event queue.
342 * @param component The component to add
344 * @see #removeInvalidComponent
346 public void addInvalidComponent(JComponent component)
348 Component validateRoot = null;
349 Component c = component;
350 while (c != null)
352 // Special cases we don't bother validating are when the invalidated
353 // component (or any of it's ancestors) is inside a CellRendererPane
354 // or if it doesn't have a peer yet (== not displayable).
355 if (c instanceof CellRendererPane || ! c.isDisplayable())
356 return;
357 if (c instanceof JComponent && ((JComponent) c).isValidateRoot())
359 validateRoot = c;
360 break;
363 c = c.getParent();
366 // If we didn't find a validate root, then we don't validate.
367 if (validateRoot == null)
368 return;
370 // Make sure the validate root and all of it's ancestors are visible.
371 c = validateRoot;
372 while (c != null)
374 if (! c.isVisible() || ! c.isDisplayable())
375 return;
376 c = c.getParent();
379 if (invalidComponents.contains(validateRoot))
380 return;
382 //synchronized (invalidComponents)
383 // {
384 invalidComponents.add(validateRoot);
385 // }
387 if (! repaintWorker.isLive())
389 repaintWorker.setLive(true);
390 invokeLater(repaintWorker);
395 * Remove a component from the {@link #invalidComponents} vector.
397 * @param component The component to remove
399 * @see #addInvalidComponent
401 public void removeInvalidComponent(JComponent component)
403 synchronized (invalidComponents)
405 invalidComponents.remove(component);
410 * Add a region to the set of dirty regions for a specified component.
411 * This involves union'ing the new region with any existing dirty region
412 * associated with the component. If the {@link #repaintWorker} class
413 * is not active, insert it in the system event queue.
415 * @param component The component to add a dirty region for
416 * @param x The left x coordinate of the new dirty region
417 * @param y The top y coordinate of the new dirty region
418 * @param w The width of the new dirty region
419 * @param h The height of the new dirty region
421 * @see #addDirtyRegion
422 * @see #getDirtyRegion
423 * @see #isCompletelyDirty
424 * @see #markCompletelyClean
425 * @see #markCompletelyDirty
427 public void addDirtyRegion(JComponent component, int x, int y,
428 int w, int h)
430 if (w <= 0 || h <= 0 || !component.isShowing())
431 return;
432 component.computeVisibleRect(rectCache);
433 SwingUtilities.computeIntersection(x, y, w, h, rectCache);
435 if (! rectCache.isEmpty())
437 synchronized (dirtyComponents)
439 Rectangle dirtyRect = (Rectangle)dirtyComponents.get(component);
440 if (dirtyRect != null)
442 SwingUtilities.computeUnion(rectCache.x, rectCache.y,
443 rectCache.width, rectCache.height,
444 dirtyRect);
446 else
448 dirtyComponents.put(component, rectCache.getBounds());
452 if (! repaintWorker.isLive())
454 repaintWorker.setLive(true);
455 invokeLater(repaintWorker);
461 * Get the dirty region associated with a component, or <code>null</code>
462 * if the component has no dirty region.
464 * @param component The component to get the dirty region of
466 * @return The dirty region of the component
468 * @see #dirtyComponents
469 * @see #addDirtyRegion
470 * @see #isCompletelyDirty
471 * @see #markCompletelyClean
472 * @see #markCompletelyDirty
474 public Rectangle getDirtyRegion(JComponent component)
476 Rectangle dirty = (Rectangle) dirtyComponents.get(component);
477 if (dirty == null)
478 dirty = new Rectangle();
479 return dirty;
483 * Mark a component as dirty over its entire bounds.
485 * @param component The component to mark as dirty
487 * @see #dirtyComponents
488 * @see #addDirtyRegion
489 * @see #getDirtyRegion
490 * @see #isCompletelyDirty
491 * @see #markCompletelyClean
493 public void markCompletelyDirty(JComponent component)
495 addDirtyRegion(component, 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
499 * Remove all dirty regions for a specified component
501 * @param component The component to mark as clean
503 * @see #dirtyComponents
504 * @see #addDirtyRegion
505 * @see #getDirtyRegion
506 * @see #isCompletelyDirty
507 * @see #markCompletelyDirty
509 public void markCompletelyClean(JComponent component)
511 synchronized (dirtyComponents)
513 dirtyComponents.remove(component);
518 * Return <code>true</code> if the specified component is completely
519 * contained within its dirty region, otherwise <code>false</code>
521 * @param component The component to check for complete dirtyness
523 * @return Whether the component is completely dirty
525 * @see #dirtyComponents
526 * @see #addDirtyRegion
527 * @see #getDirtyRegion
528 * @see #isCompletelyDirty
529 * @see #markCompletelyClean
531 public boolean isCompletelyDirty(JComponent component)
533 boolean dirty = false;
534 Rectangle r = getDirtyRegion(component);
535 if(r.width == Integer.MAX_VALUE && r.height == Integer.MAX_VALUE)
536 dirty = true;
537 return dirty;
541 * Validate all components which have been marked invalid in the {@link
542 * #invalidComponents} vector.
544 public void validateInvalidComponents()
546 // We don't use an iterator here because that would fail when there are
547 // components invalidated during the validation of others, which happens
548 // quite frequently. Instead we synchronize the access a little more.
549 while (invalidComponents.size() > 0)
551 Component comp;
552 synchronized (invalidComponents)
554 comp = (Component) invalidComponents.remove(0);
556 // Validate the validate component.
557 if (! (comp.isVisible() && comp.isShowing()))
558 continue;
559 comp.validate();
564 * Repaint all regions of all components which have been marked dirty in the
565 * {@link #dirtyComponents} table.
567 public void paintDirtyRegions()
569 // Short circuit if there is nothing to paint.
570 if (dirtyComponents.size() == 0)
571 return;
573 // Swap dirtyRegions with dirtyRegionsWork to avoid locking.
574 synchronized (dirtyComponents)
576 HashMap swap = dirtyComponents;
577 dirtyComponents = dirtyComponentsWork;
578 dirtyComponentsWork = swap;
581 // Compile a set of repaint roots.
582 HashSet repaintRoots = new HashSet();
583 Set components = dirtyComponentsWork.keySet();
584 for (Iterator i = components.iterator(); i.hasNext();)
586 JComponent dirty = (JComponent) i.next();
587 compileRepaintRoots(dirtyComponentsWork, dirty, repaintRoots);
590 for (Iterator i = repaintRoots.iterator(); i.hasNext();)
592 JComponent comp = (JComponent) i.next();
593 Rectangle damaged = (Rectangle) dirtyComponentsWork.remove(comp);
594 if (damaged == null || damaged.isEmpty())
595 continue;
596 comp.paintImmediately(damaged);
598 dirtyComponentsWork.clear();
602 * Compiles a list of components that really get repainted. This is called
603 * once for each component in the dirtyRegions HashMap, each time with
604 * another <code>dirty</code> parameter. This searches up the component
605 * hierarchy of <code>dirty</code> to find the highest parent that is also
606 * marked dirty and merges the dirty regions.
608 * @param dirtyRegions the dirty regions
609 * @param dirty the component for which to find the repaint root
610 * @param roots the list to which new repaint roots get appended
612 private void compileRepaintRoots(HashMap dirtyRegions, JComponent dirty,
613 HashSet roots)
615 Component current = dirty;
616 Component root = dirty;
618 // This will contain the dirty region in the root coordinate system,
619 // possibly clipped by ancestor's bounds.
620 Rectangle originalDirtyRect = (Rectangle) dirtyRegions.get(dirty);
621 rectCache.setBounds(originalDirtyRect);
623 // The bounds of the current component.
624 int x = dirty.getX();
625 int y = dirty.getY();
626 int w = dirty.getWidth();
627 int h = dirty.getHeight();
629 // Do nothing if dirty region is clipped away by the component's bounds.
630 rectCache = SwingUtilities.computeIntersection(0, 0, w, h, rectCache);
631 if (rectCache.isEmpty())
632 return;
634 // The cumulated offsets.
635 int dx = 0;
636 int dy = 0;
637 // The actual offset for the found root.
638 int rootDx = 0;
639 int rootDy = 0;
641 // Search the highest component that is also marked dirty.
642 Component parent;
643 while (true)
645 parent = current.getParent();
646 if (parent == null || !(parent instanceof JComponent))
647 break;
649 current = parent;
650 // Update the offset.
651 dx += x;
652 dy += y;
653 rectCache.x += x;
654 rectCache.y += y;
656 x = current.getX();
657 y = current.getY();
658 w = current.getWidth();
659 h = current.getHeight();
660 rectCache = SwingUtilities.computeIntersection(0, 0, w, h, rectCache);
662 // Don't paint if the dirty regions is clipped away by any of
663 // its ancestors.
664 if (rectCache.isEmpty())
665 return;
667 // We can skip to the next up when this parent is not dirty.
668 if (dirtyRegions.containsKey(parent))
670 root = current;
671 rootDx = dx;
672 rootDy = dy;
676 // Merge the rectangles of the root and the requested component if
677 // the are different.
678 if (root != dirty)
680 rectCache.x += rootDx - dx;
681 rectCache.y += rootDy - dy;
682 Rectangle dirtyRect = (Rectangle) dirtyRegions.get(root);
683 SwingUtilities.computeUnion(rectCache.x, rectCache.y, rectCache.width,
684 rectCache.height, dirtyRect);
687 // Adds the root to the roots set.
688 if (! roots.contains(root))
689 roots.add(root);
693 * Get an offscreen buffer for painting a component's image. This image
694 * may be smaller than the proposed dimensions, depending on the value of
695 * the {@link #doubleBufferMaximumSize} property.
697 * @param component The component to return an offscreen buffer for
698 * @param proposedWidth The proposed width of the offscreen buffer
699 * @param proposedHeight The proposed height of the offscreen buffer
701 * @return A shared offscreen buffer for painting
703 public Image getOffscreenBuffer(Component component, int proposedWidth,
704 int proposedHeight)
706 Component root = SwingUtilities.getWindowAncestor(component);
707 Image buffer = (Image) offscreenBuffers.get(root);
708 if (buffer == null
709 || buffer.getWidth(null) < proposedWidth
710 || buffer.getHeight(null) < proposedHeight)
712 int width = Math.max(proposedWidth, root.getWidth());
713 width = Math.min(doubleBufferMaximumSize.width, width);
714 int height = Math.max(proposedHeight, root.getHeight());
715 height = Math.min(doubleBufferMaximumSize.height, height);
716 buffer = component.createImage(width, height);
717 offscreenBuffers.put(root, buffer);
719 return buffer;
723 * Blits the back buffer of the specified root component to the screen.
724 * This is package private because it must get called by JComponent.
726 * @param comp the component to be painted
727 * @param x the area to paint on screen, in comp coordinates
728 * @param y the area to paint on screen, in comp coordinates
729 * @param w the area to paint on screen, in comp coordinates
730 * @param h the area to paint on screen, in comp coordinates
732 void commitBuffer(Component comp, int x, int y, int w, int h)
734 Component root = comp;
735 while (root != null
736 && ! (root instanceof Window || root instanceof Applet))
738 x += root.getX();
739 y += root.getY();
740 root = root.getParent();
743 if (root != null)
745 Graphics g = root.getGraphics();
746 Image buffer = (Image) offscreenBuffers.get(root);
747 if (buffer != null)
749 // Make sure we have a sane clip at this point.
750 g.clipRect(x, y, w, h);
751 g.drawImage(buffer, 0, 0, root);
752 g.dispose();
758 * Creates and returns a volatile offscreen buffer for the specified
759 * component that can be used as a double buffer. The returned image
760 * is a {@link VolatileImage}. Its size will be <code>(proposedWidth,
761 * proposedHeight)</code> except when the maximum double buffer size
762 * has been set in this RepaintManager.
764 * @param comp the Component for which to create a volatile buffer
765 * @param proposedWidth the proposed width of the buffer
766 * @param proposedHeight the proposed height of the buffer
768 * @since 1.4
770 * @see VolatileImage
772 public Image getVolatileOffscreenBuffer(Component comp, int proposedWidth,
773 int proposedHeight)
775 Component root = SwingUtilities.getWindowAncestor(comp);
776 Image buffer = (Image) offscreenBuffers.get(root);
777 if (buffer == null
778 || buffer.getWidth(null) < proposedWidth
779 || buffer.getHeight(null) < proposedHeight
780 || !(buffer instanceof VolatileImage))
782 int width = Math.max(proposedWidth, root.getWidth());
783 width = Math.min(doubleBufferMaximumSize.width, width);
784 int height = Math.max(proposedHeight, root.getHeight());
785 height = Math.min(doubleBufferMaximumSize.height, height);
786 buffer = root.createVolatileImage(width, height);
787 if (buffer != null)
788 offscreenBuffers.put(root, buffer);
790 return buffer;
795 * Get the value of the {@link #doubleBufferMaximumSize} property.
797 * @return The current value of the property
799 * @see #setDoubleBufferMaximumSize
801 public Dimension getDoubleBufferMaximumSize()
803 return doubleBufferMaximumSize;
807 * Set the value of the {@link #doubleBufferMaximumSize} property.
809 * @param size The new value of the property
811 * @see #getDoubleBufferMaximumSize
813 public void setDoubleBufferMaximumSize(Dimension size)
815 doubleBufferMaximumSize = size;
819 * Set the value of the {@link #doubleBufferingEnabled} property.
821 * @param buffer The new value of the property
823 * @see #isDoubleBufferingEnabled
825 public void setDoubleBufferingEnabled(boolean buffer)
827 doubleBufferingEnabled = buffer;
831 * Get the value of the {@link #doubleBufferingEnabled} property.
833 * @return The current value of the property
835 * @see #setDoubleBufferingEnabled
837 public boolean isDoubleBufferingEnabled()
839 return doubleBufferingEnabled;
842 public String toString()
844 return "RepaintManager";
848 * Sends an RepaintManagerEvent to the event queue with the specified
849 * runnable. This is similar to SwingUtilities.invokeLater(), only that the
850 * event is a low priority event in order to defer the execution a little
851 * more.
853 private void invokeLater(Runnable runnable)
855 Toolkit tk = Toolkit.getDefaultToolkit();
856 EventQueue evQueue = tk.getSystemEventQueue();
857 InvocationEvent ev = new RepaintWorkerEvent(evQueue, runnable, null, false);
858 evQueue.postEvent(ev);