blob: 91034dbf8f28eda6cfb9bc0f43049bc2ffaa33d5 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25package javax.swing;
26
27
28import java.awt.*;
29import java.awt.event.*;
30import java.awt.peer.ComponentPeer;
31import java.awt.peer.ContainerPeer;
32import java.awt.image.VolatileImage;
33import java.security.AccessController;
34import java.util.*;
35import java.applet.*;
36
37import sun.awt.AppContext;
38import sun.awt.DisplayChangedListener;
39import sun.awt.SunToolkit;
40import sun.java2d.SunGraphicsEnvironment;
41import sun.security.action.GetPropertyAction;
42
43
44/**
45 * This class manages repaint requests, allowing the number
46 * of repaints to be minimized, for example by collapsing multiple
47 * requests into a single repaint for members of a component tree.
48 * <p>
49 * As of 1.6 <code>RepaintManager</code> handles repaint requests
50 * for Swing's top level components (<code>JApplet</code>,
51 * <code>JWindow</code>, <code>JFrame</code> and <code>JDialog</code>).
52 * Any calls to <code>repaint</code> on one of these will call into the
53 * appropriate <code>addDirtyRegion</code> method.
54 *
55 * @author Arnaud Weber
56 */
57public class RepaintManager
58{
59 /**
60 * Whether or not the RepaintManager should handle paint requests
61 * for top levels.
62 */
63 static final boolean HANDLE_TOP_LEVEL_PAINT;
64
65 private static final short BUFFER_STRATEGY_NOT_SPECIFIED = 0;
66 private static final short BUFFER_STRATEGY_SPECIFIED_ON = 1;
67 private static final short BUFFER_STRATEGY_SPECIFIED_OFF = 2;
68
69 private static final short BUFFER_STRATEGY_TYPE;
70
71 /**
72 * Maps from GraphicsConfiguration to VolatileImage.
73 */
74 private Map<GraphicsConfiguration,VolatileImage> volatileMap = new
75 HashMap<GraphicsConfiguration,VolatileImage>(1);
76
77 //
78 // As of 1.6 Swing handles scheduling of paint events from native code.
79 // That is, SwingPaintEventDispatcher is invoked on the toolkit thread,
80 // which in turn invokes nativeAddDirtyRegion. Because this is invoked
81 // from the native thread we can not invoke any public methods and so
82 // we introduce these added maps. So, any time nativeAddDirtyRegion is
83 // invoked the region is added to hwDirtyComponents and a work request
84 // is scheduled. When the work request is processed all entries in
85 // this map are pushed to the real map (dirtyComponents) and then
86 // painted with the rest of the components.
87 //
88 private Map<Container,Rectangle> hwDirtyComponents;
89
90 private Map<Component,Rectangle> dirtyComponents;
91 private Map<Component,Rectangle> tmpDirtyComponents;
92 private java.util.List<Component> invalidComponents;
93
94 // List of Runnables that need to be processed before painting from AWT.
95 private java.util.List<Runnable> runnableList;
96
97 boolean doubleBufferingEnabled = true;
98
99 private Dimension doubleBufferMaxSize;
100
101 // Support for both the standard and volatile offscreen buffers exists to
102 // provide backwards compatibility for the [rare] programs which may be
103 // calling getOffScreenBuffer() and not expecting to get a VolatileImage.
104 // Swing internally is migrating to use *only* the volatile image buffer.
105
106 // Support for standard offscreen buffer
107 //
108 DoubleBufferInfo standardDoubleBuffer;
109
110 /**
111 * Object responsible for hanlding core paint functionality.
112 */
113 private PaintManager paintManager;
114
115 private static final Object repaintManagerKey = RepaintManager.class;
116
117 // Whether or not a VolatileImage should be used for double-buffered painting
118 static boolean volatileImageBufferEnabled = true;
119 /**
120 * Value of the system property awt.nativeDoubleBuffering.
121 */
122 private static boolean nativeDoubleBuffering;
123
124 // The maximum number of times Swing will attempt to use the VolatileImage
125 // buffer during a paint operation.
126 private static final int VOLATILE_LOOP_MAX = 2;
127
128 /**
129 * Number of <code>beginPaint</code> that have been invoked.
130 */
131 private int paintDepth = 0;
132
133 /**
134 * Type of buffer strategy to use. Will be one of the BUFFER_STRATEGY_
135 * constants.
136 */
137 private short bufferStrategyType;
138
139 //
140 // BufferStrategyPaintManager has the unique characteristic that it
141 // must deal with the buffer being lost while painting to it. For
142 // example, if we paint a component and show it and the buffer has
143 // become lost we must repaint the whole window. To deal with that
144 // the PaintManager calls into repaintRoot, and if we're still in
145 // the process of painting the repaintRoot field is set to the JRootPane
146 // and after the current JComponent.paintImmediately call finishes
147 // paintImmediately will be invoked on the repaintRoot. In this
148 // way we don't try to show garbage to the screen.
149 //
150 /**
151 * True if we're in the process of painting the dirty regions. This is
152 * set to true in <code>paintDirtyRegions</code>.
153 */
154 private boolean painting;
155 /**
156 * If the PaintManager calls into repaintRoot during painting this field
157 * will be set to the root.
158 */
159 private JComponent repaintRoot;
160
161 /**
162 * The Thread that has initiated painting. If null it
163 * indicates painting is not currently in progress.
164 */
165 private Thread paintThread;
166
167 /**
168 * Runnable used to process all repaint/revalidate requests.
169 */
170 private final ProcessingRunnable processingRunnable;
171
172
173 static {
174 volatileImageBufferEnabled = "true".equals(AccessController.
175 doPrivileged(new GetPropertyAction(
176 "swing.volatileImageBufferEnabled", "true")));
177 boolean headless = GraphicsEnvironment.isHeadless();
178 if (volatileImageBufferEnabled && headless) {
179 volatileImageBufferEnabled = false;
180 }
181 nativeDoubleBuffering = "true".equals(AccessController.doPrivileged(
182 new GetPropertyAction("awt.nativeDoubleBuffering")));
183 String bs = AccessController.doPrivileged(
184 new GetPropertyAction("swing.bufferPerWindow"));
185 if (headless) {
186 BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_SPECIFIED_OFF;
187 }
188 else if (bs == null) {
189 BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_NOT_SPECIFIED;
190 }
191 else if ("true".equals(bs)) {
192 BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_SPECIFIED_ON;
193 }
194 else {
195 BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_SPECIFIED_OFF;
196 }
197 HANDLE_TOP_LEVEL_PAINT = "true".equals(AccessController.doPrivileged(
198 new GetPropertyAction("swing.handleTopLevelPaint", "true")));
199 GraphicsEnvironment ge = GraphicsEnvironment.
200 getLocalGraphicsEnvironment();
201 if (ge instanceof SunGraphicsEnvironment) {
202 ((SunGraphicsEnvironment)ge).addDisplayChangedListener(
203 new DisplayChangedHandler());
204 }
205 }
206
207 /**
208 * Return the RepaintManager for the calling thread given a Component.
209 *
210 * @param c a Component -- unused in the default implementation, but could
211 * be used by an overridden version to return a different RepaintManager
212 * depending on the Component
213 * @return the RepaintManager object
214 */
215 public static RepaintManager currentManager(Component c) {
216 // Note: DisplayChangedRunnable passes in null as the component, so if
217 // component is ever used to determine the current
218 // RepaintManager, DisplayChangedRunnable will need to be modified
219 // accordingly.
220 return currentManager(AppContext.getAppContext());
221 }
222
223 /**
224 * Returns the RepaintManager for the specified AppContext. If
225 * a RepaintManager has not been created for the specified
226 * AppContext this will return null.
227 */
228 static RepaintManager currentManager(AppContext appContext) {
229 RepaintManager rm = (RepaintManager)appContext.get(repaintManagerKey);
230 if (rm == null) {
231 rm = new RepaintManager(BUFFER_STRATEGY_TYPE);
232 appContext.put(repaintManagerKey, rm);
233 }
234 return rm;
235 }
236
237 /**
238 * Return the RepaintManager for the calling thread given a JComponent.
239 * <p>
240 * Note: This method exists for backward binary compatibility with earlier
241 * versions of the Swing library. It simply returns the result returned by
242 * {@link #currentManager(Component)}.
243 *
244 * @param c a JComponent -- unused
245 * @return the RepaintManager object
246 */
247 public static RepaintManager currentManager(JComponent c) {
248 return currentManager((Component)c);
249 }
250
251
252 /**
253 * Set the RepaintManager that should be used for the calling
254 * thread. <b>aRepaintManager</b> will become the current RepaintManager
255 * for the calling thread's thread group.
256 * @param aRepaintManager the RepaintManager object to use
257 */
258 public static void setCurrentManager(RepaintManager aRepaintManager) {
259 if (aRepaintManager != null) {
260 SwingUtilities.appContextPut(repaintManagerKey, aRepaintManager);
261 } else {
262 SwingUtilities.appContextRemove(repaintManagerKey);
263 }
264 }
265
266 /**
267 * Create a new RepaintManager instance. You rarely call this constructor.
268 * directly. To get the default RepaintManager, use
269 * RepaintManager.currentManager(JComponent) (normally "this").
270 */
271 public RepaintManager() {
272 // Because we can't know what a subclass is doing with the
273 // volatile image we immediately punt in subclasses. If this
274 // poses a problem we'll need a more sophisticated detection algorithm,
275 // or API.
276 this(BUFFER_STRATEGY_SPECIFIED_OFF);
277 }
278
279 private RepaintManager(short bufferStrategyType) {
280 // If native doublebuffering is being used, do NOT use
281 // Swing doublebuffering.
282 doubleBufferingEnabled = !nativeDoubleBuffering;
283 synchronized(this) {
284 dirtyComponents = new IdentityHashMap<Component,Rectangle>();
285 tmpDirtyComponents = new IdentityHashMap<Component,Rectangle>();
286 this.bufferStrategyType = bufferStrategyType;
287 hwDirtyComponents = new IdentityHashMap<Container,Rectangle>();
288 }
289 processingRunnable = new ProcessingRunnable();
290 }
291
292 private void displayChanged() {
293 clearImages();
294 }
295
296 /**
297 * Mark the component as in need of layout and queue a runnable
298 * for the event dispatching thread that will validate the components
299 * first isValidateRoot() ancestor.
300 *
301 * @see JComponent#isValidateRoot
302 * @see #removeInvalidComponent
303 */
304 public synchronized void addInvalidComponent(JComponent invalidComponent)
305 {
306 Component validateRoot = null;
307
308 /* Find the first JComponent ancestor of this component whose
309 * isValidateRoot() method returns true.
310 */
311 for(Component c = invalidComponent; c != null; c = c.getParent()) {
312 if ((c instanceof CellRendererPane) || (c.getPeer() == null)) {
313 return;
314 }
315 if ((c instanceof JComponent) && (((JComponent)c).isValidateRoot())) {
316 validateRoot = c;
317 break;
318 }
319 }
320
321 /* There's no validateRoot to apply validate to, so we're done.
322 */
323 if (validateRoot == null) {
324 return;
325 }
326
327 /* If the validateRoot and all of its ancestors aren't visible
328 * then we don't do anything. While we're walking up the tree
329 * we find the root Window or Applet.
330 */
331 Component root = null;
332
333 for(Component c = validateRoot; c != null; c = c.getParent()) {
334 if (!c.isVisible() || (c.getPeer() == null)) {
335 return;
336 }
337 if ((c instanceof Window) || (c instanceof Applet)) {
338 root = c;
339 break;
340 }
341 }
342
343 if (root == null) {
344 return;
345 }
346
347 /* Lazily create the invalidateComponents vector and add the
348 * validateRoot if it's not there already. If this validateRoot
349 * is already in the vector, we're done.
350 */
351 if (invalidComponents == null) {
352 invalidComponents = new ArrayList<Component>();
353 }
354 else {
355 int n = invalidComponents.size();
356 for(int i = 0; i < n; i++) {
357 if(validateRoot == invalidComponents.get(i)) {
358 return;
359 }
360 }
361 }
362 invalidComponents.add(validateRoot);
363
364 // Queue a Runnable to invoke paintDirtyRegions and
365 // validateInvalidComponents.
366 scheduleProcessingRunnable();
367 }
368
369
370 /**
371 * Remove a component from the list of invalid components.
372 *
373 * @see #addInvalidComponent
374 */
375 public synchronized void removeInvalidComponent(JComponent component) {
376 if(invalidComponents != null) {
377 int index = invalidComponents.indexOf(component);
378 if(index != -1) {
379 invalidComponents.remove(index);
380 }
381 }
382 }
383
384
385 /**
386 * Add a component in the list of components that should be refreshed.
387 * If <i>c</i> already has a dirty region, the rectangle <i>(x,y,w,h)</i>
388 * will be unioned with the region that should be redrawn.
389 *
390 * @see JComponent#repaint
391 */
392 private void addDirtyRegion0(Container c, int x, int y, int w, int h) {
393 /* Special cases we don't have to bother with.
394 */
395 if ((w <= 0) || (h <= 0) || (c == null)) {
396 return;
397 }
398
399 if ((c.getWidth() <= 0) || (c.getHeight() <= 0)) {
400 return;
401 }
402
403 if (extendDirtyRegion(c, x, y, w, h)) {
404 // Component was already marked as dirty, region has been
405 // extended, no need to continue.
406 return;
407 }
408
409 /* Make sure that c and all it ancestors (up to an Applet or
410 * Window) are visible. This loop has the same effect as
411 * checking c.isShowing() (and note that it's still possible
412 * that c is completely obscured by an opaque ancestor in
413 * the specified rectangle).
414 */
415 Component root = null;
416
417 // Note: We can't synchronize around this, Frame.getExtendedState
418 // is synchronized so that if we were to synchronize around this
419 // it could lead to the possibility of getting locks out
420 // of order and deadlocking.
421 for (Container p = c; p != null; p = p.getParent()) {
422 if (!p.isVisible() || (p.getPeer() == null)) {
423 return;
424 }
425 if ((p instanceof Window) || (p instanceof Applet)) {
426 // Iconified frames are still visible!
427 if (p instanceof Frame &&
428 (((Frame)p).getExtendedState() & Frame.ICONIFIED) ==
429 Frame.ICONIFIED) {
430 return;
431 }
432 root = p;
433 break;
434 }
435 }
436
437 if (root == null) return;
438
439 synchronized(this) {
440 if (extendDirtyRegion(c, x, y, w, h)) {
441 // In between last check and this check another thread
442 // queued up runnable, can bail here.
443 return;
444 }
445 dirtyComponents.put(c, new Rectangle(x, y, w, h));
446 }
447
448 // Queue a Runnable to invoke paintDirtyRegions and
449 // validateInvalidComponents.
450 scheduleProcessingRunnable();
451 }
452
453 /**
454 * Add a component in the list of components that should be refreshed.
455 * If <i>c</i> already has a dirty region, the rectangle <i>(x,y,w,h)</i>
456 * will be unioned with the region that should be redrawn.
457 *
458 * @param c Component to repaint, null results in nothing happening.
459 * @param x X coordinate of the region to repaint
460 * @param y Y coordinate of the region to repaint
461 * @param w Width of the region to repaint
462 * @param h Height of the region to repaint
463 * @see JComponent#repaint
464 */
465 public void addDirtyRegion(JComponent c, int x, int y, int w, int h)
466 {
467 addDirtyRegion0(c, x, y, w, h);
468 }
469
470 /**
471 * Adds <code>window</code> to the list of <code>Component</code>s that
472 * need to be repainted.
473 *
474 * @param window Window to repaint, null results in nothing happening.
475 * @param x X coordinate of the region to repaint
476 * @param y Y coordinate of the region to repaint
477 * @param w Width of the region to repaint
478 * @param h Height of the region to repaint
479 * @see JFrame#repaint
480 * @see JWindow#repaint
481 * @see JDialog#repaint
482 * @since 1.6
483 */
484 public void addDirtyRegion(Window window, int x, int y, int w, int h) {
485 addDirtyRegion0(window, x, y, w, h);
486 }
487
488 /**
489 * Adds <code>applet</code> to the list of <code>Component</code>s that
490 * need to be repainted.
491 *
492 * @param applet Applet to repaint, null results in nothing happening.
493 * @param x X coordinate of the region to repaint
494 * @param y Y coordinate of the region to repaint
495 * @param w Width of the region to repaint
496 * @param h Height of the region to repaint
497 * @see JApplet#repaint
498 * @since 1.6
499 */
500 public void addDirtyRegion(Applet applet, int x, int y, int w, int h) {
501 addDirtyRegion0(applet, x, y, w, h);
502 }
503
504 void scheduleHeavyWeightPaints() {
505 Map<Container,Rectangle> hws;
506
507 synchronized(this) {
508 if (hwDirtyComponents.size() == 0) {
509 return;
510 }
511 hws = hwDirtyComponents;
512 hwDirtyComponents = new IdentityHashMap<Container,Rectangle>();
513 }
514 for (Container hw : hws.keySet()) {
515 Rectangle dirty = hws.get(hw);
516 if (hw instanceof Window) {
517 addDirtyRegion((Window)hw, dirty.x, dirty.y,
518 dirty.width, dirty.height);
519 }
520 else if (hw instanceof Applet) {
521 addDirtyRegion((Applet)hw, dirty.x, dirty.y,
522 dirty.width, dirty.height);
523 }
524 else { // SwingHeavyWeight
525 addDirtyRegion0(hw, dirty.x, dirty.y,
526 dirty.width, dirty.height);
527 }
528 }
529 }
530
531 //
532 // This is called from the toolkit thread when a native expose is
533 // received.
534 //
535 void nativeAddDirtyRegion(AppContext appContext, Container c,
536 int x, int y, int w, int h) {
537 if (w > 0 && h > 0) {
538 synchronized(this) {
539 Rectangle dirty = hwDirtyComponents.get(c);
540 if (dirty == null) {
541 hwDirtyComponents.put(c, new Rectangle(x, y, w, h));
542 }
543 else {
544 hwDirtyComponents.put(c, SwingUtilities.computeUnion(
545 x, y, w, h, dirty));
546 }
547 }
548 scheduleProcessingRunnable(appContext);
549 }
550 }
551
552 //
553 // This is called from the toolkit thread when awt needs to run a
554 // Runnable before we paint.
555 //
556 void nativeQueueSurfaceDataRunnable(AppContext appContext, Component c,
557 Runnable r) {
558 synchronized(this) {
559 if (runnableList == null) {
560 runnableList = new LinkedList<Runnable>();
561 }
562 runnableList.add(r);
563 }
564 scheduleProcessingRunnable(appContext);
565 }
566
567 /**
568 * Extends the dirty region for the specified component to include
569 * the new region.
570 *
571 * @return false if <code>c</code> is not yet marked dirty.
572 */
573 private synchronized boolean extendDirtyRegion(
574 Component c, int x, int y, int w, int h) {
575 Rectangle r = (Rectangle)dirtyComponents.get(c);
576 if (r != null) {
577 // A non-null r implies c is already marked as dirty,
578 // and that the parent is valid. Therefore we can
579 // just union the rect and bail.
580 SwingUtilities.computeUnion(x, y, w, h, r);
581 return true;
582 }
583 return false;
584 }
585
586 /** Return the current dirty region for a component.
587 * Return an empty rectangle if the component is not
588 * dirty.
589 */
590 public Rectangle getDirtyRegion(JComponent aComponent) {
591 Rectangle r = null;
592 synchronized(this) {
593 r = (Rectangle)dirtyComponents.get(aComponent);
594 }
595 if(r == null)
596 return new Rectangle(0,0,0,0);
597 else
598 return new Rectangle(r);
599 }
600
601 /**
602 * Mark a component completely dirty. <b>aComponent</b> will be
603 * completely painted during the next paintDirtyRegions() call.
604 */
605 public void markCompletelyDirty(JComponent aComponent) {
606 addDirtyRegion(aComponent,0,0,Integer.MAX_VALUE,Integer.MAX_VALUE);
607 }
608
609 /**
610 * Mark a component completely clean. <b>aComponent</b> will not
611 * get painted during the next paintDirtyRegions() call.
612 */
613 public void markCompletelyClean(JComponent aComponent) {
614 synchronized(this) {
615 dirtyComponents.remove(aComponent);
616 }
617 }
618
619 /**
620 * Convenience method that returns true if <b>aComponent</b> will be completely
621 * painted during the next paintDirtyRegions(). If computing dirty regions is
622 * expensive for your component, use this method and avoid computing dirty region
623 * if it return true.
624 */
625 public boolean isCompletelyDirty(JComponent aComponent) {
626 Rectangle r;
627
628 r = getDirtyRegion(aComponent);
629 if(r.width == Integer.MAX_VALUE &&
630 r.height == Integer.MAX_VALUE)
631 return true;
632 else
633 return false;
634 }
635
636
637 /**
638 * Validate all of the components that have been marked invalid.
639 * @see #addInvalidComponent
640 */
641 public void validateInvalidComponents() {
642 java.util.List<Component> ic;
643 synchronized(this) {
644 if(invalidComponents == null) {
645 return;
646 }
647 ic = invalidComponents;
648 invalidComponents = null;
649 }
650 int n = ic.size();
651 for(int i = 0; i < n; i++) {
652 ic.get(i).validate();
653 }
654 }
655
656
657 /**
658 * This is invoked to process paint requests. It's needed
659 * for backward compatability in so far as RepaintManager would previously
660 * not see paint requests for top levels, so, we have to make sure
661 * a subclass correctly paints any dirty top levels.
662 */
663 private void prePaintDirtyRegions() {
664 Map<Component,Rectangle> dirtyComponents;
665 java.util.List<Runnable> runnableList;
666 synchronized(this) {
667 dirtyComponents = this.dirtyComponents;
668 runnableList = this.runnableList;
669 this.runnableList = null;
670 }
671 if (runnableList != null) {
672 for (Runnable runnable : runnableList) {
673 runnable.run();
674 }
675 }
676 paintDirtyRegions();
677 if (dirtyComponents.size() > 0) {
678 // This'll only happen if a subclass isn't correctly dealing
679 // with toplevels.
680 paintDirtyRegions(dirtyComponents);
681 }
682 }
683
684 /**
685 * Paint all of the components that have been marked dirty.
686 *
687 * @see #addDirtyRegion
688 */
689 public void paintDirtyRegions() {
690 synchronized(this) { // swap for thread safety
691 Map<Component,Rectangle> tmp = tmpDirtyComponents;
692 tmpDirtyComponents = dirtyComponents;
693 dirtyComponents = tmp;
694 dirtyComponents.clear();
695 }
696 paintDirtyRegions(tmpDirtyComponents);
697 }
698
699 private void paintDirtyRegions(Map<Component,Rectangle>
700 tmpDirtyComponents){
701 int i, count;
702 java.util.List<Component> roots;
703 Component dirtyComponent;
704
705 count = tmpDirtyComponents.size();
706 if (count == 0) {
707 return;
708 }
709
710 Rectangle rect;
711 int localBoundsX = 0;
712 int localBoundsY = 0;
713 int localBoundsH = 0;
714 int localBoundsW = 0;
715 Enumeration keys;
716
717 roots = new ArrayList<Component>(count);
718
719 for (Component dirty : tmpDirtyComponents.keySet()) {
720 collectDirtyComponents(tmpDirtyComponents, dirty, roots);
721 }
722
723 count = roots.size();
724 // System.out.println("roots size is " + count);
725 painting = true;
726 try {
727 for(i=0 ; i < count ; i++) {
728 dirtyComponent = roots.get(i);
729 rect = tmpDirtyComponents.get(dirtyComponent);
730 // System.out.println("Should refresh :" + rect);
731 localBoundsH = dirtyComponent.getHeight();
732 localBoundsW = dirtyComponent.getWidth();
733
734 SwingUtilities.computeIntersection(localBoundsX,
735 localBoundsY,
736 localBoundsW,
737 localBoundsH,
738 rect);
739 if (dirtyComponent instanceof JComponent) {
740 ((JComponent)dirtyComponent).paintImmediately(
741 rect.x,rect.y,rect.width, rect.height);
742 }
743 else if (dirtyComponent.isShowing()) {
744 Graphics g = JComponent.safelyGetGraphics(
745 dirtyComponent, dirtyComponent);
746 // If the Graphics goes away, it means someone disposed of
747 // the window, don't do anything.
748 if (g != null) {
749 g.setClip(rect.x, rect.y, rect.width, rect.height);
750 try {
751 dirtyComponent.paint(g);
752 } finally {
753 g.dispose();
754 }
755 }
756 }
757 // If the repaintRoot has been set, service it now and
758 // remove any components that are children of repaintRoot.
759 if (repaintRoot != null) {
760 adjustRoots(repaintRoot, roots, i + 1);
761 count = roots.size();
762 paintManager.isRepaintingRoot = true;
763 repaintRoot.paintImmediately(0, 0, repaintRoot.getWidth(),
764 repaintRoot.getHeight());
765 paintManager.isRepaintingRoot = false;
766 // Only service repaintRoot once.
767 repaintRoot = null;
768 }
769 }
770 } finally {
771 painting = false;
772 }
773 tmpDirtyComponents.clear();
774 }
775
776
777 /**
778 * Removes any components from roots that are children of
779 * root.
780 */
781 private void adjustRoots(JComponent root,
782 java.util.List<Component> roots, int index) {
783 for (int i = roots.size() - 1; i >= index; i--) {
784 Component c = roots.get(i);
785 for(;;) {
786 if (c == root || c == null || !(c instanceof JComponent)) {
787 break;
788 }
789 c = c.getParent();
790 }
791 if (c == root) {
792 roots.remove(i);
793 }
794 }
795 }
796
797 Rectangle tmp = new Rectangle();
798
799 void collectDirtyComponents(Map<Component,Rectangle> dirtyComponents,
800 Component dirtyComponent,
801 java.util.List<Component> roots) {
802 int dx, dy, rootDx, rootDy;
803 Component component, rootDirtyComponent,parent;
804 Rectangle cBounds;
805
806 // Find the highest parent which is dirty. When we get out of this
807 // rootDx and rootDy will contain the translation from the
808 // rootDirtyComponent's coordinate system to the coordinates of the
809 // original dirty component. The tmp Rect is also used to compute the
810 // visible portion of the dirtyRect.
811
812 component = rootDirtyComponent = dirtyComponent;
813
814 int x = dirtyComponent.getX();
815 int y = dirtyComponent.getY();
816 int w = dirtyComponent.getWidth();
817 int h = dirtyComponent.getHeight();
818
819 dx = rootDx = 0;
820 dy = rootDy = 0;
821 tmp.setBounds((Rectangle) dirtyComponents.get(dirtyComponent));
822
823 // System.out.println("Collect dirty component for bound " + tmp +
824 // "component bounds is " + cBounds);;
825 SwingUtilities.computeIntersection(0,0,w,h,tmp);
826
827 if (tmp.isEmpty()) {
828 // System.out.println("Empty 1");
829 return;
830 }
831
832 for(;;) {
833 if(!(component instanceof JComponent))
834 break;
835
836 parent = component.getParent();
837 if(parent == null)
838 break;
839
840 component = parent;
841
842 dx += x;
843 dy += y;
844 tmp.setLocation(tmp.x + x, tmp.y + y);
845
846 x = component.getX();
847 y = component.getY();
848 w = component.getWidth();
849 h = component.getHeight();
850 tmp = SwingUtilities.computeIntersection(0,0,w,h,tmp);
851
852 if (tmp.isEmpty()) {
853 // System.out.println("Empty 2");
854 return;
855 }
856
857 if (dirtyComponents.get(component) != null) {
858 rootDirtyComponent = component;
859 rootDx = dx;
860 rootDy = dy;
861 }
862 }
863
864 if (dirtyComponent != rootDirtyComponent) {
865 Rectangle r;
866 tmp.setLocation(tmp.x + rootDx - dx,
867 tmp.y + rootDy - dy);
868 r = (Rectangle)dirtyComponents.get(rootDirtyComponent);
869 SwingUtilities.computeUnion(tmp.x,tmp.y,tmp.width,tmp.height,r);
870 }
871
872 // If we haven't seen this root before, then we need to add it to the
873 // list of root dirty Views.
874
875 if (!roots.contains(rootDirtyComponent))
876 roots.add(rootDirtyComponent);
877 }
878
879
880 /**
881 * Returns a string that displays and identifies this
882 * object's properties.
883 *
884 * @return a String representation of this object
885 */
886 public synchronized String toString() {
887 StringBuffer sb = new StringBuffer();
888 if(dirtyComponents != null)
889 sb.append("" + dirtyComponents);
890 return sb.toString();
891 }
892
893
894 /**
895 * Return the offscreen buffer that should be used as a double buffer with
896 * the component <code>c</code>.
897 * By default there is a double buffer per RepaintManager.
898 * The buffer might be smaller than <code>(proposedWidth,proposedHeight)</code>
899 * This happens when the maximum double buffer size as been set for the receiving
900 * repaint manager.
901 */
902 public Image getOffscreenBuffer(Component c,int proposedWidth,int proposedHeight) {
903 return _getOffscreenBuffer(c, proposedWidth, proposedHeight);
904 }
905
906 /**
907 * Return a volatile offscreen buffer that should be used as a
908 * double buffer with the specified component <code>c</code>.
909 * The image returned will be an instance of VolatileImage, or null
910 * if a VolatileImage object could not be instantiated.
911 * This buffer might be smaller than <code>(proposedWidth,proposedHeight)</code>.
912 * This happens when the maximum double buffer size has been set for this
913 * repaint manager.
914 *
915 * @see java.awt.image.VolatileImage
916 * @since 1.4
917 */
918 public Image getVolatileOffscreenBuffer(Component c,
919 int proposedWidth,int proposedHeight) {
920 GraphicsConfiguration config = c.getGraphicsConfiguration();
921 if (config == null) {
922 config = GraphicsEnvironment.getLocalGraphicsEnvironment().
923 getDefaultScreenDevice().getDefaultConfiguration();
924 }
925 Dimension maxSize = getDoubleBufferMaximumSize();
926 int width = proposedWidth < 1 ? 1 :
927 (proposedWidth > maxSize.width? maxSize.width : proposedWidth);
928 int height = proposedHeight < 1 ? 1 :
929 (proposedHeight > maxSize.height? maxSize.height : proposedHeight);
930 VolatileImage image = volatileMap.get(config);
931 if (image == null || image.getWidth() < width ||
932 image.getHeight() < height) {
933 if (image != null) {
934 image.flush();
935 }
936 image = config.createCompatibleVolatileImage(width, height);
937 volatileMap.put(config, image);
938 }
939 return image;
940 }
941
942 private Image _getOffscreenBuffer(Component c, int proposedWidth, int proposedHeight) {
943 Dimension maxSize = getDoubleBufferMaximumSize();
944 DoubleBufferInfo doubleBuffer = null;
945 int width, height;
946
947 if (standardDoubleBuffer == null) {
948 standardDoubleBuffer = new DoubleBufferInfo();
949 }
950 doubleBuffer = standardDoubleBuffer;
951
952 width = proposedWidth < 1? 1 :
953 (proposedWidth > maxSize.width? maxSize.width : proposedWidth);
954 height = proposedHeight < 1? 1 :
955 (proposedHeight > maxSize.height? maxSize.height : proposedHeight);
956
957 if (doubleBuffer.needsReset || (doubleBuffer.image != null &&
958 (doubleBuffer.size.width < width ||
959 doubleBuffer.size.height < height))) {
960 doubleBuffer.needsReset = false;
961 if (doubleBuffer.image != null) {
962 doubleBuffer.image.flush();
963 doubleBuffer.image = null;
964 }
965 width = Math.max(doubleBuffer.size.width, width);
966 height = Math.max(doubleBuffer.size.height, height);
967 }
968
969 Image result = doubleBuffer.image;
970
971 if (doubleBuffer.image == null) {
972 result = c.createImage(width , height);
973 doubleBuffer.size = new Dimension(width, height);
974 if (c instanceof JComponent) {
975 ((JComponent)c).setCreatedDoubleBuffer(true);
976 doubleBuffer.image = result;
977 }
978 // JComponent will inform us when it is no longer valid
979 // (via removeNotify) we have no such hook to other components,
980 // therefore we don't keep a ref to the Component
981 // (indirectly through the Image) by stashing the image.
982 }
983 return result;
984 }
985
986
987 /** Set the maximum double buffer size. **/
988 public void setDoubleBufferMaximumSize(Dimension d) {
989 doubleBufferMaxSize = d;
990 if (doubleBufferMaxSize == null) {
991 clearImages();
992 } else {
993 clearImages(d.width, d.height);
994 }
995 }
996
997 private void clearImages() {
998 clearImages(0, 0);
999 }
1000
1001 private void clearImages(int width, int height) {
1002 if (standardDoubleBuffer != null && standardDoubleBuffer.image != null) {
1003 if (standardDoubleBuffer.image.getWidth(null) > width ||
1004 standardDoubleBuffer.image.getHeight(null) > height) {
1005 standardDoubleBuffer.image.flush();
1006 standardDoubleBuffer.image = null;
1007 }
1008 }
1009 // Clear out the VolatileImages
1010 Iterator gcs = volatileMap.keySet().iterator();
1011 while (gcs.hasNext()) {
1012 GraphicsConfiguration gc = (GraphicsConfiguration)gcs.next();
1013 VolatileImage image = (VolatileImage)volatileMap.get(gc);
1014 if (image.getWidth() > width || image.getHeight() > height) {
1015 image.flush();
1016 gcs.remove();
1017 }
1018 }
1019 }
1020
1021 /**
1022 * Returns the maximum double buffer size.
1023 *
1024 * @return a Dimension object representing the maximum size
1025 */
1026 public Dimension getDoubleBufferMaximumSize() {
1027 if (doubleBufferMaxSize == null) {
1028 try {
1029 Rectangle virtualBounds = new Rectangle();
1030 GraphicsEnvironment ge = GraphicsEnvironment.
1031 getLocalGraphicsEnvironment();
1032 for (GraphicsDevice gd : ge.getScreenDevices()) {
1033 GraphicsConfiguration gc = gd.getDefaultConfiguration();
1034 virtualBounds = virtualBounds.union(gc.getBounds());
1035 }
1036 doubleBufferMaxSize = new Dimension(virtualBounds.width,
1037 virtualBounds.height);
1038 } catch (HeadlessException e) {
1039 doubleBufferMaxSize = new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
1040 }
1041 }
1042 return doubleBufferMaxSize;
1043 }
1044
1045 /**
1046 * Enables or disables double buffering in this RepaintManager.
1047 * CAUTION: The default value for this property is set for optimal
1048 * paint performance on the given platform and it is not recommended
1049 * that programs modify this property directly.
1050 *
1051 * @param aFlag true to activate double buffering
1052 * @see #isDoubleBufferingEnabled
1053 */
1054 public void setDoubleBufferingEnabled(boolean aFlag) {
1055 doubleBufferingEnabled = aFlag;
1056 PaintManager paintManager = getPaintManager();
1057 if (!aFlag && paintManager.getClass() != PaintManager.class) {
1058 setPaintManager(new PaintManager());
1059 }
1060 }
1061
1062 /**
1063 * Returns true if this RepaintManager is double buffered.
1064 * The default value for this property may vary from platform
1065 * to platform. On platforms where native double buffering
1066 * is supported in the AWT, the default value will be <code>false</code>
1067 * to avoid unnecessary buffering in Swing.
1068 * On platforms where native double buffering is not supported,
1069 * the default value will be <code>true</code>.
1070 *
1071 * @return true if this object is double buffered
1072 */
1073 public boolean isDoubleBufferingEnabled() {
1074 return doubleBufferingEnabled;
1075 }
1076
1077 /**
1078 * This resets the double buffer. Actually, it marks the double buffer
1079 * as invalid, the double buffer will then be recreated on the next
1080 * invocation of getOffscreenBuffer.
1081 */
1082 void resetDoubleBuffer() {
1083 if (standardDoubleBuffer != null) {
1084 standardDoubleBuffer.needsReset = true;
1085 }
1086 }
1087
1088 /**
1089 * This resets the volatile double buffer.
1090 */
1091 void resetVolatileDoubleBuffer(GraphicsConfiguration gc) {
1092 Image image = volatileMap.remove(gc);
1093 if (image != null) {
1094 image.flush();
1095 }
1096 }
1097
1098 /**
1099 * Returns true if we should use the <code>Image</code> returned
1100 * from <code>getVolatileOffscreenBuffer</code> to do double buffering.
1101 */
1102 boolean useVolatileDoubleBuffer() {
1103 return volatileImageBufferEnabled;
1104 }
1105
1106 /**
1107 * Returns true if the current thread is the thread painting. This
1108 * will return false if no threads are painting.
1109 */
1110 private synchronized boolean isPaintingThread() {
1111 return (Thread.currentThread() == paintThread);
1112 }
1113 //
1114 // Paint methods. You very, VERY rarely need to invoke these.
1115 // They are invoked directly from JComponent's painting code and
1116 // when painting happens outside the normal flow: DefaultDesktopManager
1117 // and JViewport. If you end up needing these methods in other places be
1118 // careful that you don't get stuck in a paint loop.
1119 //
1120
1121 /**
1122 * Paints a region of a component
1123 *
1124 * @param paintingComponent Component to paint
1125 * @param bufferComponent Component to obtain buffer for
1126 * @param g Graphics to paint to
1127 * @param x X-coordinate
1128 * @param y Y-coordinate
1129 * @param w Width
1130 * @param h Height
1131 */
1132 void paint(JComponent paintingComponent,
1133 JComponent bufferComponent, Graphics g,
1134 int x, int y, int w, int h) {
1135 PaintManager paintManager = getPaintManager();
1136 if (!isPaintingThread()) {
1137 // We're painting to two threads at once. PaintManager deals
1138 // with this a bit better than BufferStrategyPaintManager, use
1139 // it to avoid possible exceptions/corruption.
1140 if (paintManager.getClass() != PaintManager.class) {
1141 paintManager = new PaintManager();
1142 paintManager.repaintManager = this;
1143 }
1144 }
1145 if (!paintManager.paint(paintingComponent, bufferComponent, g,
1146 x, y, w, h)) {
1147 g.setClip(x, y, w, h);
1148 paintingComponent.paintToOffscreen(g, x, y, w, h, x + w, y + h);
1149 }
1150 }
1151
1152 /**
1153 * Does a copy area on the specified region.
1154 *
1155 * @param clip Whether or not the copyArea needs to be clipped to the
1156 * Component's bounds.
1157 */
1158 void copyArea(JComponent c, Graphics g, int x, int y, int w, int h,
1159 int deltaX, int deltaY, boolean clip) {
1160 getPaintManager().copyArea(c, g, x, y, w, h, deltaX, deltaY, clip);
1161 }
1162
1163 /**
1164 * Invoked prior to any paint/copyArea method calls. This will
1165 * be followed by an invocation of <code>endPaint</code>.
1166 * <b>WARNING</b>: Callers of this method need to wrap the call
1167 * in a <code>try/finally</code>, otherwise if an exception is thrown
1168 * during the course of painting the RepaintManager may
1169 * be left in a state in which the screen is not updated, eg:
1170 * <pre>
1171 * repaintManager.beginPaint();
1172 * try {
1173 * repaintManager.paint(...);
1174 * } finally {
1175 * repaintManager.endPaint();
1176 * }
1177 * </pre>
1178 */
1179 void beginPaint() {
1180 boolean multiThreadedPaint = false;
1181 int paintDepth = 0;
1182 Thread currentThread = Thread.currentThread();
1183 synchronized(this) {
1184 paintDepth = this.paintDepth;
1185 if (paintThread == null || currentThread == paintThread) {
1186 paintThread = currentThread;
1187 this.paintDepth++;
1188 } else {
1189 multiThreadedPaint = true;
1190 }
1191 }
1192 if (!multiThreadedPaint && paintDepth == 0) {
1193 getPaintManager().beginPaint();
1194 }
1195 }
1196
1197 /**
1198 * Invoked after <code>beginPaint</code> has been invoked.
1199 */
1200 void endPaint() {
1201 if (isPaintingThread()) {
1202 PaintManager paintManager = null;
1203 synchronized(this) {
1204 if (--paintDepth == 0) {
1205 paintManager = getPaintManager();
1206 }
1207 }
1208 if (paintManager != null) {
1209 paintManager.endPaint();
1210 synchronized(this) {
1211 paintThread = null;
1212 }
1213 }
1214 }
1215 }
1216
1217 /**
1218 * If possible this will show a previously rendered portion of
1219 * a Component. If successful, this will return true, otherwise false.
1220 * <p>
1221 * WARNING: This method is invoked from the native toolkit thread, be
1222 * very careful as to what methods this invokes!
1223 */
1224 boolean show(Container c, int x, int y, int w, int h) {
1225 return getPaintManager().show(c, x, y, w, h);
1226 }
1227
1228 /**
1229 * Invoked when the doubleBuffered or useTrueDoubleBuffering
1230 * properties of a JRootPane change. This may come in on any thread.
1231 */
1232 void doubleBufferingChanged(JRootPane rootPane) {
1233 getPaintManager().doubleBufferingChanged(rootPane);
1234 }
1235
1236 /**
1237 * Sets the <code>PaintManager</code> that is used to handle all
1238 * double buffered painting.
1239 *
1240 * @param paintManager The PaintManager to use. Passing in null indicates
1241 * the fallback PaintManager should be used.
1242 */
1243 void setPaintManager(PaintManager paintManager) {
1244 if (paintManager == null) {
1245 paintManager = new PaintManager();
1246 }
1247 PaintManager oldPaintManager;
1248 synchronized(this) {
1249 oldPaintManager = this.paintManager;
1250 this.paintManager = paintManager;
1251 paintManager.repaintManager = this;
1252 }
1253 if (oldPaintManager != null) {
1254 oldPaintManager.dispose();
1255 }
1256 }
1257
1258 private synchronized PaintManager getPaintManager() {
1259 if (paintManager == null) {
1260 PaintManager paintManager = null;
1261 if (doubleBufferingEnabled && !nativeDoubleBuffering) {
1262 switch (bufferStrategyType) {
1263 case BUFFER_STRATEGY_NOT_SPECIFIED:
1264 if (((SunToolkit)Toolkit.getDefaultToolkit()).
1265 useBufferPerWindow()) {
1266 paintManager = new BufferStrategyPaintManager();
1267 }
1268 break;
1269 case BUFFER_STRATEGY_SPECIFIED_ON:
1270 paintManager = new BufferStrategyPaintManager();
1271 break;
1272 default:
1273 break;
1274 }
1275 }
1276 // null case handled in setPaintManager
1277 setPaintManager(paintManager);
1278 }
1279 return paintManager;
1280 }
1281
1282 private void scheduleProcessingRunnable() {
1283 scheduleProcessingRunnable(AppContext.getAppContext());
1284 }
1285
1286 private void scheduleProcessingRunnable(AppContext context) {
1287 if (processingRunnable.markPending()) {
1288 SunToolkit.getSystemEventQueueImplPP(context).
1289 postEvent(new InvocationEvent(Toolkit.getDefaultToolkit(),
1290 processingRunnable));
1291 }
1292 }
1293
1294
1295 /**
1296 * PaintManager is used to handle all double buffered painting for
1297 * Swing. Subclasses should call back into the JComponent method
1298 * <code>paintToOffscreen</code> to handle the actual painting.
1299 */
1300 static class PaintManager {
1301 /**
1302 * RepaintManager the PaintManager has been installed on.
1303 */
1304 protected RepaintManager repaintManager;
1305 boolean isRepaintingRoot;
1306
1307 /**
1308 * Paints a region of a component
1309 *
1310 * @param paintingComponent Component to paint
1311 * @param bufferComponent Component to obtain buffer for
1312 * @param g Graphics to paint to
1313 * @param x X-coordinate
1314 * @param y Y-coordinate
1315 * @param w Width
1316 * @param h Height
1317 * @return true if painting was successful.
1318 */
1319 public boolean paint(JComponent paintingComponent,
1320 JComponent bufferComponent, Graphics g,
1321 int x, int y, int w, int h) {
1322 // First attempt to use VolatileImage buffer for performance.
1323 // If this fails (which should rarely occur), fallback to a
1324 // standard Image buffer.
1325 boolean paintCompleted = false;
1326 Image offscreen;
1327 if (repaintManager.useVolatileDoubleBuffer() &&
1328 (offscreen = getValidImage(repaintManager.
1329 getVolatileOffscreenBuffer(bufferComponent, w, h))) != null) {
1330 VolatileImage vImage = (java.awt.image.VolatileImage)offscreen;
1331 GraphicsConfiguration gc = bufferComponent.
1332 getGraphicsConfiguration();
1333 for (int i = 0; !paintCompleted &&
1334 i < RepaintManager.VOLATILE_LOOP_MAX; i++) {
1335 if (vImage.validate(gc) ==
1336 VolatileImage.IMAGE_INCOMPATIBLE) {
1337 repaintManager.resetVolatileDoubleBuffer(gc);
1338 offscreen = repaintManager.getVolatileOffscreenBuffer(
1339 bufferComponent,w, h);
1340 vImage = (java.awt.image.VolatileImage)offscreen;
1341 }
1342 paintDoubleBuffered(paintingComponent, vImage, g, x, y,
1343 w, h);
1344 paintCompleted = !vImage.contentsLost();
1345 }
1346 }
1347 // VolatileImage painting loop failed, fallback to regular
1348 // offscreen buffer
1349 if (!paintCompleted && (offscreen = getValidImage(
1350 repaintManager.getOffscreenBuffer(
1351 bufferComponent, w, h))) != null) {
1352 paintDoubleBuffered(paintingComponent, offscreen, g, x, y, w,
1353 h);
1354 paintCompleted = true;
1355 }
1356 return paintCompleted;
1357 }
1358
1359 /**
1360 * Does a copy area on the specified region.
1361 */
1362 public void copyArea(JComponent c, Graphics g, int x, int y, int w,
1363 int h, int deltaX, int deltaY, boolean clip) {
1364 g.copyArea(x, y, w, h, deltaX, deltaY);
1365 }
1366
1367 /**
1368 * Invoked prior to any calls to paint or copyArea.
1369 */
1370 public void beginPaint() {
1371 }
1372
1373 /**
1374 * Invoked to indicate painting has been completed.
1375 */
1376 public void endPaint() {
1377 }
1378
1379 /**
1380 * Shows a region of a previously rendered component. This
1381 * will return true if successful, false otherwise. The default
1382 * implementation returns false.
1383 */
1384 public boolean show(Container c, int x, int y, int w, int h) {
1385 return false;
1386 }
1387
1388 /**
1389 * Invoked when the doubleBuffered or useTrueDoubleBuffering
1390 * properties of a JRootPane change. This may come in on any thread.
1391 */
1392 public void doubleBufferingChanged(JRootPane rootPane) {
1393 }
1394
1395 /**
1396 * Paints a portion of a component to an offscreen buffer.
1397 */
1398 protected void paintDoubleBuffered(JComponent c, Image image,
1399 Graphics g, int clipX, int clipY,
1400 int clipW, int clipH) {
1401 Graphics osg = image.getGraphics();
1402 int bw = Math.min(clipW, image.getWidth(null));
1403 int bh = Math.min(clipH, image.getHeight(null));
1404 int x,y,maxx,maxy;
1405
1406 try {
1407 for(x = clipX, maxx = clipX+clipW; x < maxx ; x += bw ) {
1408 for(y=clipY, maxy = clipY + clipH; y < maxy ; y += bh) {
1409 osg.translate(-x, -y);
1410 osg.setClip(x,y,bw,bh);
1411 c.paintToOffscreen(osg, x, y, bw, bh, maxx, maxy);
1412 g.setClip(x, y, bw, bh);
1413 g.drawImage(image, x, y, c);
1414 osg.translate(x, y);
1415 }
1416 }
1417 } finally {
1418 osg.dispose();
1419 }
1420 }
1421
1422 /**
1423 * If <code>image</code> is non-null with a positive size it
1424 * is returned, otherwise null is returned.
1425 */
1426 private Image getValidImage(Image image) {
1427 if (image != null && image.getWidth(null) > 0 &&
1428 image.getHeight(null) > 0) {
1429 return image;
1430 }
1431 return null;
1432 }
1433
1434 /**
1435 * Schedules a repaint for the specified component. This differs
1436 * from <code>root.repaint</code> in that if the RepaintManager is
1437 * currently processing paint requests it'll process this request
1438 * with the current set of requests.
1439 */
1440 protected void repaintRoot(JComponent root) {
1441 assert (repaintManager.repaintRoot == null);
1442 if (repaintManager.painting) {
1443 repaintManager.repaintRoot = root;
1444 }
1445 else {
1446 root.repaint();
1447 }
1448 }
1449
1450 /**
1451 * Returns true if the component being painted is the root component
1452 * that was previously passed to <code>repaintRoot</code>.
1453 */
1454 protected boolean isRepaintingRoot() {
1455 return isRepaintingRoot;
1456 }
1457
1458 /**
1459 * Cleans up any state. After invoked the PaintManager will no
1460 * longer be used anymore.
1461 */
1462 protected void dispose() {
1463 }
1464 }
1465
1466
1467 private class DoubleBufferInfo {
1468 public Image image;
1469 public Dimension size;
1470 public boolean needsReset = false;
1471 }
1472
1473
1474 /**
1475 * Listener installed to detect display changes. When display changes,
1476 * schedules a callback to notify all RepaintManagers of the display
1477 * changes. Only one DisplayChangedHandler is ever installed. The
1478 * singleton instance will schedule notification for all AppContexts.
1479 */
1480 private static final class DisplayChangedHandler implements
1481 DisplayChangedListener {
1482 public void displayChanged() {
1483 scheduleDisplayChanges();
1484 }
1485
1486 public void paletteChanged() {
1487 }
1488
1489 private void scheduleDisplayChanges() {
1490 // To avoid threading problems, we notify each RepaintManager
1491 // on the thread it was created on.
1492 for (Object c : AppContext.getAppContexts()) {
1493 AppContext context = (AppContext) c;
1494 synchronized(context) {
1495 if (!context.isDisposed()) {
1496 EventQueue eventQueue = (EventQueue)context.get(
1497 AppContext.EVENT_QUEUE_KEY);
1498 if (eventQueue != null) {
1499 eventQueue.postEvent(new InvocationEvent(
1500 Toolkit.getDefaultToolkit(),
1501 new DisplayChangedRunnable()));
1502 }
1503 }
1504 }
1505 }
1506 }
1507 }
1508
1509
1510 private static final class DisplayChangedRunnable implements Runnable {
1511 public void run() {
1512 RepaintManager.currentManager((JComponent)null).displayChanged();
1513 }
1514 }
1515
1516
1517 /**
1518 * Runnable used to process all repaint/revalidate requests.
1519 */
1520 private final class ProcessingRunnable implements Runnable {
1521 // If true, we're wainting on the EventQueue.
1522 private boolean pending;
1523
1524 /**
1525 * Marks this processing runnable as pending. If this was not
1526 * already marked as pending, true is returned.
1527 */
1528 public synchronized boolean markPending() {
1529 if (!pending) {
1530 pending = true;
1531 return true;
1532 }
1533 return false;
1534 }
1535
1536 public void run() {
1537 synchronized (this) {
1538 pending = false;
1539 }
1540 // First pass, flush any heavy paint events into real paint
1541 // events. If there are pending heavy weight requests this will
1542 // result in q'ing this request up one more time. As
1543 // long as no other requests come in between now and the time
1544 // the second one is processed nothing will happen. This is not
1545 // ideal, but the logic needed to suppress the second request is
1546 // more headache than it's worth.
1547 scheduleHeavyWeightPaints();
1548 // Do the actual validation and painting.
1549 validateInvalidComponents();
1550 prePaintDirtyRegions();
1551 }
1552 }
1553}