001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package com.sun.perseus.model;
028:
029: import java.util.Vector;
030:
031: import com.sun.perseus.j2d.Box;
032: import com.sun.perseus.j2d.RGB;
033: import com.sun.perseus.j2d.Tile;
034: import com.sun.perseus.j2d.RenderGraphics;
035:
036: import java.io.InputStream;
037:
038: /**
039: * <p>The <code>DirtyAreaManager</code> abstraction is responsible for tracking
040: * areas of the document tree which need to be repainted.
041: *
042: * @version $Id: DirtyAreaManager.java,v 1.16 2006/06/29 10:47:30 ln156897 Exp $
043: */
044: public class DirtyAreaManager extends UpdateAdapter {
045: /**
046: * TEMPORARY: GLOBAL VARIABLE ALLOWING US TO CONTROL WHETHER DIRTY AREA
047: * TRACKING IS ON OR OFF.
048: */
049: public static boolean ON = false;
050:
051: /**
052: * The minimal tile width or height.
053: */
054: public static final int DEFAULT_TILE_MIN_SIZE = 40;
055:
056: /**
057: * The list of modified nodes. Some may have been removed from the tree.
058: */
059: Vector dirtyNodes = new Vector();
060: /**
061: * The Root tile representing this surface.
062: */
063: TileElement rootTile;
064:
065: /**
066: * The associated viewport, which defines the size of the rendering
067: * area.
068: */
069: Viewport vp;
070:
071: /**
072: * The minimal size, in both dimensions, for dirty area tiles.
073: */
074: int tileMinSize = DEFAULT_TILE_MIN_SIZE;
075:
076: /**
077: * Width of the viewport last time it was painted.
078: */
079: int lastWidth = -1;
080:
081: /**
082: * Height of the viewport last time it was painted.
083: */
084: int lastHeight = -1;
085:
086: /**
087: * The last RenderGraphics for which dirty areas were checked.
088: */
089: RenderGraphics lastRG;
090:
091: /**
092: * @param vp the associated viewport.
093: */
094: public DirtyAreaManager(Viewport vp) {
095: setViewport(vp);
096: }
097:
098: /**
099: * @param vp the new associated viewport.
100: */
101: public void setViewport(Viewport vp) {
102: if (vp == this .vp) {
103: return;
104: }
105:
106: this .vp = vp;
107:
108: // Reset the lastWidth and lastHeight values to force a full repaint
109: // on the first getDirtyAreas call.
110: lastWidth = -1;
111: lastHeight = -1;
112: }
113:
114: /**
115: * @param tileMinSize the minimal size for a rendering tile.
116: */
117: public void setTileMinSize(final int tileMinSize) {
118: if (tileMinSize < 1) {
119: throw new IllegalArgumentException();
120: }
121:
122: this .tileMinSize = tileMinSize;
123: lastWidth = -1;
124: lastHeight = -1;
125: }
126:
127: // =========================================================================
128: // UpdateAdapter extension
129: // =========================================================================
130:
131: /**
132: * Invoked when a node has been inserted into the tree
133: *
134: * @param node the newly inserted node
135: */
136: public void nodeInserted(ModelNode node) {
137: // We only keep track of nodes which actually have a rendering.
138: if (!dirtyNodes.contains(node) && node.hasNodeRendering()) {
139: dirtyNodes.addElement(node);
140: }
141: }
142:
143: /**
144: * Invoked when a node's Rendering is about to be modified
145: *
146: * @param node the node which is about to be modified
147: */
148: public void modifyingNodeRendering(ModelNode node) {
149: if (!dirtyNodes.contains(node)) {
150: dirtyNodes.addElement(node);
151: }
152: }
153:
154: // =========================================================================
155: // End of UpdateAdapter extension
156: // =========================================================================
157:
158: /**
159: * It is the responsibility of the dirty area manager to compute the list
160: * of dirty areas which need to be re-painted. The minimum includes the
161: * area covered by the modified node's old and new bounds. For efficiency
162: * purposes, the implementation may collapse the rectangles into a few ones.
163: *
164: * @param rg the RenderGraphics for which dirty areas are queried. The first
165: * tile a RenderGraphics is passed to this method, the full area is
166: * considered dirty.
167: * @return the set of rectangles to render, based on the
168: * list of node whose rendering has changed.
169: */
170: public TileElement getDirtyAreas(RenderGraphics rg) {
171: int vw = vp.width;
172: int vh = vp.height;
173:
174: if (lastWidth != vw || lastHeight != vh) {
175: // System.err.println(">>>>>>> lastWidth was : " + lastWidth
176: // + " lastHeight was " + lastHeight);
177: // System.err.println(">>>>>>> new width is : " + vw
178: // + " new height is " + vh);
179:
180: lastWidth = vw;
181: lastHeight = vh;
182: if (vw >= (2 * tileMinSize) && vh >= (2 * tileMinSize)) {
183: rootTile = new TileQuadrant(null, tileMinSize, 0, 0,
184: vw, vh);
185: } else {
186: rootTile = new TileElement(null, 0, 0, vw, vh);
187: }
188:
189: // This is the first rendering or the viewport dimensions have
190: // changed. Everything is dirty. We need to repaint the full
191: // canvas.
192: dirtyNodes.removeAllElements();
193: lastRG = rg;
194: return rootTile;
195: }
196:
197: // If it is the first time we render in this RenderGraphics, we consider
198: // the complete area dirty.
199: if (lastRG != rg) {
200: System.err.println(">>>> RenderGraphics changed");
201: lastRG = rg;
202: dirtyNodes.removeAllElements();
203: return rootTile;
204: }
205:
206: // Unmark all areas
207: rootTile.clear();
208:
209: final int nn = dirtyNodes.size();
210: Tile nb = null;
211: ModelNode n = null;
212: for (int i = 0; i < nn && !rootTile.hit; i++) {
213: n = (ModelNode) dirtyNodes.elementAt(i);
214:
215: // First, check the previous rendering's tile.
216: rootTile.checkHit(n.getLastRenderedTile());
217:
218: // Clear the last rendered tile so that the area is not
219: // unnecessarily repainted in the future, in case this node is, for
220: // example, hidden for a while before being repainted.
221: n.clearLastRenderedTile();
222:
223: // Now, compute the current rendering tile and check the
224: // area it hits.
225: if (n.parent != null && (n.canRenderState == 0)) {
226: rootTile.checkHit(n.getRenderingTile());
227: }
228: }
229:
230: dirtyNodes.removeAllElements();
231:
232: return rootTile.getHitTiles(null);
233: }
234:
235: /**
236: * Asks the DirtyAreaManager to repaint all the diry areas on the given
237: * RenderGraphics
238: *
239: * @param mn the model node to render, typically, the DocumentNode.
240: * @param rg the RenderGraphics to render to.
241: * @param clearPaint the color to use to clear the background
242: */
243: public void refresh(final ModelNode mn, final RenderGraphics rg,
244: final RGB clearPaint) {
245: TileElement dirtyAreaList = getDirtyAreas(rg);
246: TileElement curTile = dirtyAreaList;
247: int nTiles = 0;
248: while (curTile != null) {
249: // System.err.println("Painting tile : " + curTile
250: // + " in thread " + Thread.currentThread());
251: // System.err.println("clearing with paint : " + clearPaint);
252: // System.err.println("Painting on buffer of size : "
253: // + rg.bi.getWidth() + "/" + rg.bi.getHeight());
254: rg.setRenderingTile(curTile);
255: rg.clearRect(curTile.x, curTile.y, curTile.maxX - curTile.x
256: + 1, curTile.maxY - curTile.y + 1, clearPaint);
257: mn.paint(rg);
258: curTile = curTile.next;
259:
260: /*
261: javax.swing.JOptionPane.showMessageDialog(
262: null,
263: "Current Offscreen : " + rg.bi.getWidth() + " / "
264: + rg.bi.getHeight(),
265: "Debug",
266: 0,
267: new javax.swing.ImageIcon(rg.bi));
268: */
269: }
270: }
271:
272: public static class TileElement extends Tile {
273: /**
274: * Parent tile.
275: */
276: TileQuadrant parent;
277:
278: /**
279: * Boolean marker showing the tile is fully hit.
280: * If true, it means the tile and all its children have been hit.
281: */
282: boolean hit;
283:
284: /**
285: * The next tile in the rendering tile chain. Tiles are chained to
286: * describe the list of rendering areas.
287: */
288: public TileElement next;
289:
290: /**
291: * @param parent the parent TileQuadrant.
292: * @param x the tile's origin along the x-axis. Should be positive or
293: * zero.
294: * @param y the tile's origin along the y-axis. Should be positive or
295: * zero.
296: * @param width the tile's width
297: * @param height the tile's height
298: */
299: TileElement(final TileQuadrant parent, final int x,
300: final int y, final int width, final int height) {
301: this .parent = parent;
302: if (x < 0 || y < 0) {
303: throw new IllegalArgumentException();
304: }
305: setTile(x, y, width, height);
306: }
307:
308: /**
309: * Checks if this tile is hit by the input tile. This must set the hit
310: * flag to true if this tile, and all its children (if any) are hit or
311: * have been hit by previous calls to this method.
312: *
313: * @param t the tile to check against.
314: */
315: void checkHit(final Tile t) {
316: if (t == null) {
317: return;
318: }
319:
320: if (isHit(t)) {
321: hit = true;
322: if (parent != null) {
323: parent.notifyTileHit(this );
324: }
325: }
326: }
327:
328: /**
329: * Clears the hit flag.
330: */
331: public void clear() {
332: hit = false;
333: next = null;
334: }
335:
336: /**
337: * @param rt the TileElement to chain to.
338: * @return the dirty area, i..e. the chained list of hit children.
339: */
340: TileElement getHitTiles(TileElement rt) {
341: if (hit) {
342: next = rt;
343: return this ;
344: }
345:
346: return rt;
347: }
348: }
349:
350: public static class TileQuadrant extends TileElement {
351: /**
352: * Children tiles.
353: */
354: TileElement[] children;
355:
356: /**
357: * @param parentTile the parent TileQuadrant
358: * @param tileMinSize the minimal size for tiles.
359: * @param x the tile's origin along the x-axis
360: * @param y the tile's origin along the y-axis
361: * @param width the tile's width
362: * @param height the tile's height
363: */
364: public TileQuadrant(final TileQuadrant parent,
365: final int tileMinSize, final int x, final int y,
366: final int width, final int height) {
367: super (parent, x, y, width, height);
368:
369: if (width < (2 * tileMinSize) || height < (2 * tileMinSize)) {
370: throw new IllegalArgumentException();
371: }
372:
373: int cw = width / 2;
374: int ch = height / 2;
375:
376: if (((width / 4) < tileMinSize)
377: || ((height / 4) < tileMinSize)) {
378: // We should not subdivide any more because this quadrant's tile
379: // cannot be subdivided without going below the minimal tile
380: // size.
381: children = new TileElement[4];
382: children[0] = new TileElement(this , x, y, cw, ch);
383: children[1] = new TileElement(this , x + cw, y, width
384: - cw, ch);
385: children[2] = new TileElement(this , x, y + ch, cw,
386: height - ch);
387: children[3] = new TileElement(this , x + cw, y + ch,
388: width - cw, height - ch);
389: } else {
390: children = new TileQuadrant[4];
391: children[0] = new TileQuadrant(this , tileMinSize, x, y,
392: cw, ch);
393: children[1] = new TileQuadrant(this , tileMinSize, x
394: + cw, y, width - cw, ch);
395: children[2] = new TileQuadrant(this , tileMinSize, x, y
396: + ch, cw, height - ch);
397: children[3] = new TileQuadrant(this , tileMinSize, x
398: + cw, y + ch, width - cw, height - ch);
399: }
400: }
401:
402: /**
403: * Checks if this tile is hit by the input tile.
404: *
405: * @param t the tile to check against.
406: */
407: void checkHit(final Tile t) {
408: if (t == null) {
409: return;
410: }
411:
412: if (hit) {
413: // The tile is already hit, no need to check
414: // any further.
415: return;
416: }
417:
418: if (isHit(t)) {
419: for (int i = 0; i < 4; i++) {
420: children[i].checkHit(t);
421: }
422: }
423: }
424:
425: /**
426: * Called by a child tile when it is hit.
427: */
428: void notifyTileHit(Tile child) {
429: // Simply check if all the quadrants have been hit. If so, notify
430: // parent in turn.
431: if (children[0].hit && children[1].hit && children[2].hit
432: && children[3].hit) {
433: hit = true;
434: if (parent != null) {
435: parent.notifyTileHit(this );
436: }
437: }
438: }
439:
440: /**
441: * Clears the hit flag.
442: */
443: public void clear() {
444: super .clear();
445: children[0].clear();
446: children[1].clear();
447: children[2].clear();
448: children[3].clear();
449: }
450:
451: /**
452: * @param rt the TileElement to chain to.
453: * @return the dirty area, i..e. the chained list of hit children.
454: */
455: TileElement getHitTiles(TileElement rt) {
456: if (hit) {
457: next = rt;
458: return this ;
459: }
460:
461: for (int i = 0; i < 4; i++) {
462: rt = children[i].getHitTiles(rt);
463: }
464:
465: return rt;
466: }
467:
468: }
469: }
|