001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /**
019: * @author Anton Avtamonov
020: * @version $Revision$
021: */package javax.swing;
022:
023: import java.awt.Color;
024: import java.awt.Component;
025: import java.awt.Container;
026: import java.awt.Dimension;
027: import java.awt.EventQueue;
028: import java.awt.Graphics;
029: import java.awt.GraphicsEnvironment;
030: import java.awt.Image;
031: import java.awt.Rectangle;
032: import java.awt.Toolkit;
033: import java.awt.Window;
034: import java.awt.image.VolatileImage;
035: import java.util.ArrayList;
036: import java.util.Collections;
037: import java.util.HashMap;
038: import java.util.HashSet;
039: import java.util.Hashtable;
040: import java.util.Iterator;
041: import java.util.List;
042: import java.util.Map;
043: import java.util.Set;
044:
045: import org.apache.harmony.awt.ClipRegion;
046: import org.apache.harmony.awt.ComponentInternals;
047: import org.apache.harmony.awt.gl.MultiRectArea;
048:
049: public class RepaintManager {
050: private Set invalidRoots = Collections
051: .synchronizedSet(new HashSet());
052: private Image offscreenImage;
053: private VolatileImage volatileOffscreenImage;
054:
055: private Dimension maximumSize;
056: private boolean doubleBufferingEnabled = true;
057: private Map dirtyRegions = new Hashtable();
058: private Map optimizedDirtyRegions = new HashMap();
059: private int numberOfScheduledPaintEvents;
060:
061: private static final Rectangle COMPLETELY_DIRTY_RECT = new Rectangle(
062: 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
063:
064: private static RepaintManager instance;
065:
066: public RepaintManager() {
067: maximumSize = GraphicsEnvironment.getLocalGraphicsEnvironment()
068: .isHeadlessInstance() ? new Dimension(0, 0) : Toolkit
069: .getDefaultToolkit().getScreenSize();
070: }
071:
072: private final Runnable paintEvent = new Runnable() {
073: public void run() {
074: boolean shouldPaint = false;
075: synchronized (RepaintManager.this ) {
076: numberOfScheduledPaintEvents--;
077: shouldPaint = numberOfScheduledPaintEvents == 0;
078: }
079: if (shouldPaint) {
080: validateInvalidComponents();
081: paintDirtyRegions();
082: }
083: }
084: };
085:
086: public static RepaintManager currentManager(final Component c) {
087: if (instance == null) {
088: instance = new RepaintManager();
089: }
090:
091: return instance;
092: }
093:
094: public static RepaintManager currentManager(final JComponent c) {
095: return currentManager((Component) c);
096: }
097:
098: public static void setCurrentManager(
099: final RepaintManager repaintManager) {
100: instance = repaintManager;
101: }
102:
103: /**
104: * Method doesn't perform component invalidation. It just adds component
105: * validation root to the list of roots waiting for validation and schedules
106: * it.
107: */
108: public void addInvalidComponent(final JComponent invalidComponent) {
109: // implementation is done according to the black-box testing and contradict to the
110: // spec: component is not marked as invalid (needed layout)
111: final Component root = getValidationRoot(invalidComponent);
112: if (root != null && !invalidRoots.contains(root)
113: && !root.isValid() && root.isShowing()) {
114: invalidRoots.add(root);
115: scheduleProcessingEvent();
116: }
117: }
118:
119: public void removeInvalidComponent(final JComponent component) {
120: invalidRoots.remove(component);
121: }
122:
123: public void validateInvalidComponents() {
124: while (!invalidRoots.isEmpty()) {
125: List processingRoots;
126: synchronized (invalidRoots) {
127: processingRoots = new ArrayList(invalidRoots);
128: invalidRoots.clear();
129: }
130: for (Iterator it = processingRoots.iterator(); it.hasNext();) {
131: Component c = (Component) it.next();
132: c.validate();
133: }
134: }
135: }
136:
137: public void addDirtyRegion(final JComponent c, final int x,
138: final int y, final int w, final int h) {
139: if (c == null || w <= 0 || h <= 0 || !c.isShowing()) {
140: return;
141: }
142:
143: Window ancestingWindow = SwingUtilities.getWindowAncestor(c);
144: if (ancestingWindow == null
145: || !ComponentInternals.getComponentInternals()
146: .wasPainted(ancestingWindow)) {
147: return;
148: }
149:
150: Rectangle dirtyRect = new Rectangle(x, y, w, h);
151: MultiRectArea previousValue = (MultiRectArea) dirtyRegions
152: .get(c);
153: if (previousValue != null) {
154: previousValue.add(dirtyRect);
155: } else {
156: dirtyRegions.put(c, new MultiRectArea(dirtyRect));
157: }
158:
159: scheduleProcessingEvent();
160: }
161:
162: public Rectangle getDirtyRegion(final JComponent c) {
163: MultiRectArea result = (MultiRectArea) dirtyRegions.get(c);
164: return result != null ? result.getBounds() : new Rectangle();
165: }
166:
167: public void markCompletelyDirty(final JComponent c) {
168: addDirtyRegion(c, COMPLETELY_DIRTY_RECT.x,
169: COMPLETELY_DIRTY_RECT.y, COMPLETELY_DIRTY_RECT.width,
170: COMPLETELY_DIRTY_RECT.height);
171: }
172:
173: public void markCompletelyClean(final JComponent c) {
174: dirtyRegions.remove(c);
175: }
176:
177: public boolean isCompletelyDirty(final JComponent c) {
178: MultiRectArea dirtyRect = (MultiRectArea) dirtyRegions.get(c);
179: if (dirtyRect == null) {
180: return false;
181: }
182: Rectangle dirtyBounds = dirtyRect.getBounds();
183: return dirtyBounds.width == COMPLETELY_DIRTY_RECT.width
184: && dirtyBounds.height == COMPLETELY_DIRTY_RECT.height;
185: }
186:
187: public void paintDirtyRegions() {
188: prepareOptimizedDirtyRegions();
189: for (Iterator it = optimizedDirtyRegions.entrySet().iterator(); it
190: .hasNext();) {
191: Map.Entry entry = (Map.Entry) it.next();
192: MultiRectArea repaintRegion = (MultiRectArea) entry
193: .getValue();
194: if (!repaintRegion.isEmpty()) {
195: ((JComponent) entry.getKey())
196: .paintImmediately(new ClipRegion(repaintRegion));
197: }
198: }
199: }
200:
201: // According to Spec as a offscreen buffer we can use offscreen image of any type,
202: // which can keep own content. Our implementation of VolatileImage based on GDI Bitmap and
203: // can't lost content. For performance reason as a offscreen buffer we use VolatileImage.
204: public Image getOffscreenBuffer(final Component c,
205: final int proposedWidth, final int proposedHeight) {
206: int adjustedWidth = Math.min(proposedWidth, maximumSize.width);
207: int adjustedHeight = Math.min(proposedHeight,
208: maximumSize.height);
209:
210: if (offscreenImage != null
211: && offscreenImage.getWidth(c) >= adjustedWidth
212: && offscreenImage.getHeight(c) >= adjustedHeight) {
213:
214: fillImage(offscreenImage, c.getBackground(), adjustedWidth,
215: adjustedHeight);
216: } else {
217: if (offscreenImage != null) {
218: offscreenImage.flush();
219: }
220:
221: offscreenImage = c.createVolatileImage(adjustedWidth,
222: adjustedHeight);
223: }
224:
225: return offscreenImage;
226: }
227:
228: public Image getVolatileOffscreenBuffer(final Component c,
229: final int proposedWidth, final int proposedHeight) {
230: int adjustedWidth = Math.min(proposedWidth, maximumSize.width);
231: int adjustedHeight = Math.min(proposedHeight,
232: maximumSize.height);
233:
234: if (volatileOffscreenImage != null
235: && volatileOffscreenImage.getWidth(c) >= adjustedWidth
236: && volatileOffscreenImage.getHeight(c) >= adjustedHeight) {
237:
238: if (volatileOffscreenImage.contentsLost()) {
239: volatileOffscreenImage.validate(c
240: .getGraphicsConfiguration());
241: }
242:
243: fillImage(volatileOffscreenImage, c.getBackground(),
244: adjustedWidth, adjustedHeight);
245: } else {
246: if (volatileOffscreenImage != null) {
247: volatileOffscreenImage.flush();
248: }
249:
250: volatileOffscreenImage = c.createVolatileImage(
251: adjustedWidth, adjustedHeight);
252: }
253:
254: return volatileOffscreenImage;
255: }
256:
257: public void setDoubleBufferMaximumSize(final Dimension d) {
258: maximumSize = d;
259: }
260:
261: public Dimension getDoubleBufferMaximumSize() {
262: return maximumSize;
263: }
264:
265: public void setDoubleBufferingEnabled(final boolean isEnabled) {
266: doubleBufferingEnabled = isEnabled;
267: }
268:
269: public boolean isDoubleBufferingEnabled() {
270: return doubleBufferingEnabled;
271: }
272:
273: /*
274: * That is not the best way to do scheduling.
275: * There are two issues with it:
276: * 1. Each repaint() call puts an event to the queue. All such events excepting
277: * the latest one do nothing, but queue is loaded anyway
278: * 2. There could be a case when no painting is done at all.
279: * Such situation could be if repaint() is scheduled regularly.
280: * <code>
281: * An example:
282: * final Runnable overload = new Runnable() {
283: * public void run() {
284: * SwingUtilities.invokeLater(this);
285: * component.repaint();
286: * }
287: * };
288: * </code>
289: * In this example component will never be painted since because every time
290: * repaint() is processed another repaint is sceduled and therefore effective
291: * painting is delayed.
292: */
293: private void scheduleProcessingEvent() {
294: synchronized (this ) {
295: numberOfScheduledPaintEvents++;
296: }
297: EventQueue.invokeLater(paintEvent);
298: }
299:
300: private Component getValidationRoot(final Component c) {
301: if (c == null) {
302: return null;
303: }
304: Component root = c;
305: while (!(root instanceof JComponent)
306: || !((JComponent) root).isValidateRoot()) {
307: Container parent = root.getParent();
308:
309: if (parent == null) {
310: break;
311: } else {
312: root = parent;
313: }
314: }
315: return root;
316: }
317:
318: private Map prepareOptimizedDirtyRegions() {
319: optimizedDirtyRegions.clear();
320: Set dirtyRegionsCopy;
321: synchronized (dirtyRegions) {
322: dirtyRegionsCopy = new HashSet(dirtyRegions.entrySet());
323: dirtyRegions.clear();
324: }
325: for (Iterator dirties = dirtyRegionsCopy.iterator(); dirties
326: .hasNext();) {
327: Map.Entry entry = (Map.Entry) dirties.next();
328: JComponent c = (JComponent) entry.getKey();
329: MultiRectArea dirtyRect = (MultiRectArea) entry.getValue();
330: dirtyRect.intersect(c.getVisibleRect());
331:
332: if (mergeWithParent(c, dirtyRect)) {
333: continue;
334: }
335: if (mergeWithChildren(c, dirtyRect)) {
336: continue;
337: }
338: optimizedDirtyRegions.put(c, dirtyRect);
339: }
340:
341: return optimizedDirtyRegions;
342: }
343:
344: private boolean mergeWithParent(final Component comp,
345: final MultiRectArea compDirtyRegion) {
346: Iterator optimized = optimizedDirtyRegions.entrySet()
347: .iterator();
348: while (optimized.hasNext()) {
349: Map.Entry optEntry = (Map.Entry) optimized.next();
350: JComponent optC = (JComponent) optEntry.getKey();
351: MultiRectArea optDirtyRegion = (MultiRectArea) optEntry
352: .getValue();
353: if (SwingUtilities.isDescendingFrom(comp, optC)) {
354: ClipRegion.convertRegion(comp, compDirtyRegion, optC);
355: optDirtyRegion.add(compDirtyRegion);
356: return true;
357: }
358: }
359:
360: return false;
361: }
362:
363: private boolean mergeWithChildren(final Component comp,
364: final MultiRectArea compDirtyRegion) {
365: Iterator optimized = optimizedDirtyRegions.entrySet()
366: .iterator();
367: boolean foundChildren = false;
368: while (optimized.hasNext()) {
369: Map.Entry optEntry = (Map.Entry) optimized.next();
370: JComponent optC = (JComponent) optEntry.getKey();
371: MultiRectArea optDirtyRegion = (MultiRectArea) optEntry
372: .getValue();
373: if (SwingUtilities.isDescendingFrom(optC, comp)) {
374: ClipRegion.convertRegion(optC, optDirtyRegion, comp);
375: compDirtyRegion.add(optDirtyRegion);
376: optimized.remove();
377: foundChildren = true;
378: }
379: }
380: if (foundChildren) {
381: optimizedDirtyRegions.put(comp, compDirtyRegion);
382: }
383:
384: return foundChildren;
385: }
386:
387: private static void fillImage(final Image image, final Color c,
388: final int width, final int height) {
389: Graphics g = image.getGraphics();
390: g.setColor(c);
391: g.fillRect(0, 0, width, height);
392: g.dispose();
393: }
394: }
|