1 |
/* RepaintManager.java -- |
/* RepaintManager.java -- |
2 |
Copyright (C) 2002, 2004 Free Software Foundation, Inc. |
Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. |
3 |
|
|
4 |
This file is part of GNU Classpath. |
This file is part of GNU Classpath. |
5 |
|
|
43 |
import java.awt.Image; |
import java.awt.Image; |
44 |
import java.awt.Rectangle; |
import java.awt.Rectangle; |
45 |
import java.awt.image.VolatileImage; |
import java.awt.image.VolatileImage; |
46 |
import java.util.Enumeration; |
import java.util.ArrayList; |
47 |
|
import java.util.Collections; |
48 |
|
import java.util.Comparator; |
49 |
import java.util.HashMap; |
import java.util.HashMap; |
|
import java.util.Hashtable; |
|
50 |
import java.util.Iterator; |
import java.util.Iterator; |
|
import java.util.Map; |
|
|
import java.util.Vector; |
|
51 |
|
|
52 |
/** |
/** |
53 |
* <p>The repaint manager holds a set of dirty regions, invalid components, |
* <p>The repaint manager holds a set of dirty regions, invalid components, |
110 |
|
|
111 |
} |
} |
112 |
|
|
113 |
|
/** |
114 |
|
* Compares two components using their depths in the component hierarchy. |
115 |
|
* A component with a lesser depth (higher level components) are sorted |
116 |
|
* before components with a deeper depth (low level components). This is used |
117 |
|
* to order paint requests, so that the higher level components are painted |
118 |
|
* before the low level components get painted. |
119 |
|
* |
120 |
|
* @author Roman Kennke (kennke@aicas.com) |
121 |
|
*/ |
122 |
|
private class ComponentComparator implements Comparator |
123 |
|
{ |
124 |
|
|
125 |
|
/** |
126 |
|
* Compares two components. |
127 |
|
* |
128 |
|
* @param o1 the first component |
129 |
|
* @param o2 the second component |
130 |
|
* |
131 |
|
* @return a negative integer, if <code>o1</code> is higher in the |
132 |
|
* hierarchy than <code>o2</code>, zero, if both are at the same |
133 |
|
* level and a positive integer, if <code>o1</code> is deeper in |
134 |
|
* the hierarchy than <code>o2</code> |
135 |
|
*/ |
136 |
|
public int compare(Object o1, Object o2) |
137 |
|
{ |
138 |
|
if (o1 instanceof JComponent && o2 instanceof JComponent) |
139 |
|
{ |
140 |
|
JComponent c1 = (JComponent) o1; |
141 |
|
JComponent c2 = (JComponent) o2; |
142 |
|
return getDepth(c1) - getDepth(c2); |
143 |
|
} |
144 |
|
else |
145 |
|
throw new ClassCastException("This comparator can only be used with " |
146 |
|
+ "JComponents"); |
147 |
|
} |
148 |
|
|
149 |
|
/** |
150 |
|
* Computes the depth for a given JComponent. |
151 |
|
* |
152 |
|
* @param c the component to compute the depth for |
153 |
|
* |
154 |
|
* @return the depth of the component |
155 |
|
*/ |
156 |
|
private int getDepth(JComponent c) |
157 |
|
{ |
158 |
|
Component comp = c; |
159 |
|
int depth = 0; |
160 |
|
while (comp != null) |
161 |
|
{ |
162 |
|
comp = comp.getParent(); |
163 |
|
depth++; |
164 |
|
} |
165 |
|
return depth; |
166 |
|
} |
167 |
|
} |
168 |
|
|
169 |
/** |
/** |
170 |
* A table storing the dirty regions of components. The keys of this |
* A table storing the dirty regions of components. The keys of this |
171 |
* table are components, the values are rectangles. Each component maps |
* table are components, the values are rectangles. Each component maps |
178 |
* @see #markCompletelyClean |
* @see #markCompletelyClean |
179 |
* @see #markCompletelyDirty |
* @see #markCompletelyDirty |
180 |
*/ |
*/ |
181 |
Hashtable dirtyComponents; |
HashMap dirtyComponents; |
182 |
|
|
183 |
|
HashMap workDirtyComponents; |
184 |
|
|
185 |
|
/** |
186 |
|
* Stores the order in which the components get repainted. |
187 |
|
*/ |
188 |
|
ArrayList repaintOrder; |
189 |
|
ArrayList workRepaintOrder; |
190 |
|
|
191 |
|
/** |
192 |
|
* The comparator used for ordered inserting into the repaintOrder list. |
193 |
|
*/ |
194 |
|
Comparator comparator; |
195 |
|
|
196 |
/** |
/** |
197 |
* A single, shared instance of the helper class. Any methods which mark |
* A single, shared instance of the helper class. Any methods which mark |
214 |
* @see #removeInvalidComponent |
* @see #removeInvalidComponent |
215 |
* @see #validateInvalidComponents |
* @see #validateInvalidComponents |
216 |
*/ |
*/ |
217 |
Vector invalidComponents; |
ArrayList invalidComponents; |
218 |
|
ArrayList workInvalidComponents; |
219 |
|
|
220 |
/** |
/** |
221 |
* Whether or not double buffering is enabled on this repaint |
* Whether or not double buffering is enabled on this repaint |
222 |
* manager. This is merely a hint to clients; the RepaintManager will |
* manager. This is merely a hint to clients; the RepaintManager will |
223 |
* always return an offscreen buffer when one is requested. |
* always return an offscreen buffer when one is requested. |
224 |
* |
* |
225 |
* @see #getDoubleBufferingEnabled |
* @see #isDoubleBufferingEnabled |
226 |
* @see #setDoubleBufferingEnabled |
* @see #setDoubleBufferingEnabled |
227 |
*/ |
*/ |
228 |
boolean doubleBufferingEnabled; |
boolean doubleBufferingEnabled; |
253 |
* components in all windows. This is package-private to avoid an accessor |
* components in all windows. This is package-private to avoid an accessor |
254 |
* method. |
* method. |
255 |
* |
* |
256 |
* @see #currentManager |
* @see #currentManager(JComponent) |
257 |
* @see #setCurrentManager |
* @see #setCurrentManager |
258 |
*/ |
*/ |
259 |
static RepaintManager globalManager; |
static RepaintManager globalManager; |
263 |
*/ |
*/ |
264 |
public RepaintManager() |
public RepaintManager() |
265 |
{ |
{ |
266 |
dirtyComponents = new Hashtable(); |
dirtyComponents = new HashMap(); |
267 |
invalidComponents = new Vector(); |
workDirtyComponents = new HashMap(); |
268 |
|
repaintOrder = new ArrayList(); |
269 |
|
workRepaintOrder = new ArrayList(); |
270 |
|
invalidComponents = new ArrayList(); |
271 |
|
workInvalidComponents = new ArrayList(); |
272 |
repaintWorker = new RepaintWorker(); |
repaintWorker = new RepaintWorker(); |
273 |
doubleBufferMaximumSize = new Dimension(2000,2000); |
doubleBufferMaximumSize = new Dimension(2000,2000); |
274 |
doubleBufferingEnabled = true; |
doubleBufferingEnabled = true; |
364 |
*/ |
*/ |
365 |
public synchronized void removeInvalidComponent(JComponent component) |
public synchronized void removeInvalidComponent(JComponent component) |
366 |
{ |
{ |
367 |
invalidComponents.removeElement(component); |
invalidComponents.remove(component); |
368 |
} |
} |
369 |
|
|
370 |
/** |
/** |
388 |
public synchronized void addDirtyRegion(JComponent component, int x, int y, |
public synchronized void addDirtyRegion(JComponent component, int x, int y, |
389 |
int w, int h) |
int w, int h) |
390 |
{ |
{ |
391 |
if (w == 0 || h == 0) |
if (w == 0 || h == 0 || !component.isShowing()) |
392 |
return; |
return; |
|
|
|
393 |
Rectangle r = new Rectangle(x, y, w, h); |
Rectangle r = new Rectangle(x, y, w, h); |
394 |
if (dirtyComponents.containsKey(component)) |
if (dirtyComponents.containsKey(component)) |
395 |
r = r.union((Rectangle)dirtyComponents.get(component)); |
r = r.union((Rectangle)dirtyComponents.get(component)); |
396 |
|
else |
397 |
|
insertInRepaintOrder(component); |
398 |
dirtyComponents.put(component, r); |
dirtyComponents.put(component, r); |
399 |
if (! repaintWorker.isLive()) |
if (! repaintWorker.isLive()) |
400 |
{ |
{ |
402 |
SwingUtilities.invokeLater(repaintWorker); |
SwingUtilities.invokeLater(repaintWorker); |
403 |
} |
} |
404 |
} |
} |
405 |
|
|
406 |
|
/** |
407 |
|
* Inserts a component into the repaintOrder list in an ordered fashion, |
408 |
|
* using a binary search. |
409 |
|
* |
410 |
|
* @param c the component to be inserted |
411 |
|
*/ |
412 |
|
private void insertInRepaintOrder(JComponent c) |
413 |
|
{ |
414 |
|
if (comparator == null) |
415 |
|
comparator = new ComponentComparator(); |
416 |
|
int insertIndex = Collections.binarySearch(repaintOrder, c, comparator); |
417 |
|
if (insertIndex < 0) |
418 |
|
insertIndex = -(insertIndex + 1); |
419 |
|
repaintOrder.add(insertIndex, c); |
420 |
|
} |
421 |
|
|
422 |
/** |
/** |
423 |
* Get the dirty region associated with a component, or <code>null</code> |
* Get the dirty region associated with a component, or <code>null</code> |
424 |
* if the component has no dirty region. |
* if the component has no dirty region. |
435 |
*/ |
*/ |
436 |
public Rectangle getDirtyRegion(JComponent component) |
public Rectangle getDirtyRegion(JComponent component) |
437 |
{ |
{ |
438 |
return (Rectangle) dirtyComponents.get(component); |
Rectangle dirty = (Rectangle) dirtyComponents.get(component); |
439 |
|
if (dirty == null) |
440 |
|
dirty = new Rectangle(); |
441 |
|
return dirty; |
442 |
} |
} |
443 |
|
|
444 |
/** |
/** |
456 |
{ |
{ |
457 |
Rectangle r = component.getBounds(); |
Rectangle r = component.getBounds(); |
458 |
addDirtyRegion(component, r.x, r.y, r.width, r.height); |
addDirtyRegion(component, r.x, r.y, r.width, r.height); |
459 |
|
component.isCompletelyDirty = true; |
460 |
} |
} |
461 |
|
|
462 |
/** |
/** |
472 |
*/ |
*/ |
473 |
public void markCompletelyClean(JComponent component) |
public void markCompletelyClean(JComponent component) |
474 |
{ |
{ |
475 |
dirtyComponents.remove(component); |
synchronized (this) |
476 |
|
{ |
477 |
|
dirtyComponents.remove(component); |
478 |
|
} |
479 |
|
component.isCompletelyDirty = false; |
480 |
} |
} |
481 |
|
|
482 |
/** |
/** |
495 |
*/ |
*/ |
496 |
public boolean isCompletelyDirty(JComponent component) |
public boolean isCompletelyDirty(JComponent component) |
497 |
{ |
{ |
498 |
Rectangle dirty = (Rectangle) dirtyComponents.get(component); |
if (! dirtyComponents.containsKey(component)) |
|
if (dirty == null) |
|
499 |
return false; |
return false; |
500 |
Rectangle r = component.getBounds(); |
return component.isCompletelyDirty; |
|
if (r == null) |
|
|
return true; |
|
|
return dirty.contains(r); |
|
501 |
} |
} |
502 |
|
|
503 |
/** |
/** |
506 |
*/ |
*/ |
507 |
public void validateInvalidComponents() |
public void validateInvalidComponents() |
508 |
{ |
{ |
509 |
for (Enumeration e = invalidComponents.elements(); e.hasMoreElements(); ) |
// In order to keep the blocking of application threads minimal, we switch |
510 |
|
// the invalidComponents field with the workInvalidComponents field and |
511 |
|
// work with the workInvalidComponents field. |
512 |
|
synchronized(this) |
513 |
|
{ |
514 |
|
ArrayList swap = invalidComponents; |
515 |
|
invalidComponents = workInvalidComponents; |
516 |
|
workInvalidComponents = swap; |
517 |
|
} |
518 |
|
for (Iterator i = workInvalidComponents.iterator(); i.hasNext(); ) |
519 |
{ |
{ |
520 |
JComponent comp = (JComponent) e.nextElement(); |
JComponent comp = (JComponent) i.next(); |
521 |
if (! (comp.isVisible() && comp.isShowing())) |
if (! (comp.isVisible() && comp.isShowing())) |
522 |
continue; |
continue; |
523 |
comp.validate(); |
comp.validate(); |
524 |
} |
} |
525 |
invalidComponents.clear(); |
workInvalidComponents.clear(); |
526 |
} |
} |
527 |
|
|
528 |
/** |
/** |
529 |
* Repaint all regions of all components which have been marked dirty in |
* Repaint all regions of all components which have been marked dirty in |
530 |
* the {@link #dirtyComponents} table. |
* the {@link #dirtyComponents} table. |
531 |
*/ |
*/ |
532 |
public void paintDirtyRegions() |
public synchronized void paintDirtyRegions() |
533 |
{ |
{ |
534 |
// step 1: pull out roots and calculate spanning damage |
// In order to keep the blocking of application threads minimal, we switch |
535 |
|
// the dirtyComponents field with the workdirtyComponents field and the |
536 |
HashMap roots = new HashMap(); |
// repaintOrder field with the workRepaintOrder field and work with the |
537 |
for (Enumeration e = dirtyComponents.keys(); e.hasMoreElements(); ) |
// work* fields. |
538 |
|
synchronized(this) |
539 |
|
{ |
540 |
|
ArrayList swap = workRepaintOrder; |
541 |
|
workRepaintOrder = repaintOrder; |
542 |
|
repaintOrder = swap; |
543 |
|
HashMap swap2 = workDirtyComponents; |
544 |
|
workDirtyComponents = dirtyComponents; |
545 |
|
dirtyComponents = swap2; |
546 |
|
} |
547 |
|
for (Iterator i = workRepaintOrder.iterator(); i.hasNext();) |
548 |
{ |
{ |
549 |
JComponent comp = (JComponent) e.nextElement(); |
JComponent comp = (JComponent) i.next(); |
550 |
if (! (comp.isVisible() && comp.isShowing())) |
// If a component is marked completely clean in the meantime, then skip |
551 |
|
// it. |
552 |
|
Rectangle damaged = (Rectangle) workDirtyComponents.get(comp); |
553 |
|
if (damaged == null || damaged.isEmpty()) |
554 |
continue; |
continue; |
555 |
Rectangle damaged = getDirtyRegion(comp); |
comp.paintImmediately(damaged); |
|
if (damaged.width == 0 || damaged.height == 0) |
|
|
continue; |
|
|
JRootPane root = comp.getRootPane(); |
|
|
// If the component has no root, no repainting will occur. |
|
|
if (root == null) |
|
|
continue; |
|
|
Rectangle rootDamage = SwingUtilities.convertRectangle(comp, damaged, root); |
|
|
if (! roots.containsKey(root)) |
|
|
{ |
|
|
roots.put(root, rootDamage); |
|
|
} |
|
|
else |
|
|
{ |
|
|
roots.put(root, ((Rectangle)roots.get(root)).union(rootDamage)); |
|
|
} |
|
|
} |
|
|
dirtyComponents.clear(); |
|
|
|
|
|
// step 2: paint those roots |
|
|
Iterator i = roots.entrySet().iterator(); |
|
|
while(i.hasNext()) |
|
|
{ |
|
|
Map.Entry ent = (Map.Entry) i.next(); |
|
|
JRootPane root = (JRootPane) ent.getKey(); |
|
|
Rectangle rect = (Rectangle) ent.getValue(); |
|
|
root.paintImmediately(rect); |
|
556 |
} |
} |
557 |
|
workRepaintOrder.clear(); |
558 |
|
workDirtyComponents.clear(); |
559 |
} |
} |
560 |
|
|
561 |
/** |
/** |