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.midp.chameleon;
028:
029: import com.sun.midp.chameleon.skins.ScreenSkin;
030: import com.sun.midp.chameleon.layers.BackgroundLayer;
031: import javax.microedition.lcdui.*;
032:
033: /**
034: * This class is a top-level "window" in Chameleon. A window is
035: * a collection of other layers and serves to maintain a z-ordering
036: * of those layers. The window also contains the complex repaint logic
037: * to support semi-transparent windows, their dirty regions, and the
038: * rectangle logic to repaint other layers of the window when necessary.
039: */
040: public abstract class CWindow {
041:
042: /**
043: * An array holding the bounds of this window. The indices are
044: * as follows:
045: * 0 = window's 'x' coordinate
046: * 1 = window's 'y' coordinate
047: * 2 = window's width
048: * 3 = window's height
049: *
050: * Note: The window's x and y coordinate can only be interpreted
051: * by some outside entity. For example, if some sort of manager
052: * was in charge of overseeing the placement of windows on the
053: * screen, it could do so by using the x and y coordinate values
054: * of this window's bounds.
055: */
056: protected int[] bounds;
057:
058: /**
059: * Flag indicating that at least one layer belonging to this
060: * window is in need of repainting
061: */
062: protected boolean dirty;
063:
064: /**
065: * Ordered bi-directional list with all the layers of this window.
066: */
067: protected CLayerList layers;
068:
069: /** The number of dirty layers to repaint */
070: protected int dirtyCount;
071:
072: /** Initial maximal number of the dirty layers */
073: protected int dirtyMaxCount = 10;
074:
075: /** Layers replication to not keep the lock on painting */
076: protected CLayer[] dirtyLayers = new CLayer[dirtyMaxCount];
077:
078: /**
079: * Background layer of this window, should be the bottom most layer
080: * of the window, can be invisible for transparent windows.
081: */
082: protected BackgroundLayer bgLayer;
083:
084: /** Cache values for the clip rectangle */
085: protected int cX, cY, cW, cH;
086:
087: /** Cache values for the graphics translation */
088: protected int tranX, tranY;
089:
090: /** Cache value for the graphics font */
091: protected Font font;
092:
093: /** Cache value for the graphics foreground color */
094: protected int color;
095:
096: /**
097: * Construct a new CWindow given the background image and color.
098: * If the background image is null, the fill color will be used
099: * instead. In the case null image and negative color are specified
100: * the window is considered to be transparent.
101: *
102: * @param bgImage the background image to use for the window background
103: * @param bgColor the background fill color in 0xrrggbbaa format to use
104: * for the window background if the background image is null.
105: */
106: public CWindow(Image bgImage, int bgColor) {
107: bounds = new int[4];
108: bounds[X] = 0;
109: bounds[Y] = 0;
110: bounds[W] = ScreenSkin.WIDTH;
111: bounds[H] = ScreenSkin.HEIGHT;
112:
113: layers = new CLayerList();
114:
115: /* Add the most bottom background layer */
116: bgLayer = new BackgroundLayer(bgImage, bgColor);
117: bgLayer.setBounds(0, 0, ScreenSkin.WIDTH, ScreenSkin.HEIGHT);
118: addLayer(bgLayer);
119: }
120:
121: /** Resize window and its background according to updated skin values */
122: public void resize() {
123: bounds[W] = ScreenSkin.WIDTH;
124: bounds[H] = ScreenSkin.HEIGHT;
125: bgLayer.setBounds(0, 0, ScreenSkin.WIDTH, ScreenSkin.HEIGHT);
126: }
127:
128: /**
129: * Add a new CLayer to the "deck" of layers associated
130: * with this CWindow. This method will sequentially add
131: * layers to the window, placing subsequently added layers
132: * on top of previously added layers.
133: *
134: * @param layer the new layer to add to this window
135: * @return true if new layer was added, false otherwise
136: */
137: public boolean addLayer(CLayer layer) {
138: if (layer != null) {
139: if (CGraphicsQ.DEBUG) {
140: System.err.println("Add Layer: " + layer);
141: }
142: synchronized (layers) {
143: if (layers.find(layer) == null) {
144: layer.owner = this ;
145: layers.addLayer(layer);
146: layer.addDirtyRegion();
147: requestRepaint();
148: layer.addNotify();
149: return true;
150: }
151: }
152: }
153: return false;
154: }
155:
156: /**
157: * Remove a layer from this CWindow. This method will remove
158: * the given layer from the "deck" of layers associated with
159: * this CWindow. If successfull, this method will return true,
160: * false otherwise (for example, if the layer does not belong
161: * to this window).
162: *
163: * @param layer the layer to remove from this window
164: * @return true if successful, false otherwise
165: */
166: public boolean removeLayer(CLayer layer) {
167: synchronized (layers) {
168: CLayerElement le = sweepLayer(layer);
169: if (le != null) {
170: if (CGraphicsQ.DEBUG) {
171: System.err.println("Remove Layer: " + layer);
172: }
173: layer.owner = null;
174: requestRepaint();
175: layers.removeLayerElement(le);
176: layer.removeNotify(this );
177: return true;
178: }
179: }
180: return false;
181: }
182:
183: /**
184: * Move layer to anotger location
185: * @param newBounds new bounds for this layer
186: * @param x New 'x' coordinate of the layer's origin
187: * @param y New 'y' coordinate of the layer's origin
188: * @param w New width of the layer
189: * @param h New height of the layer
190:
191: * @return true if successful, false otherwise
192: */
193: public boolean relocateLayer(CLayer layer, int x, int y, int w,
194: int h) {
195: if (layer != null) {
196: synchronized (layers) {
197: if (sweepLayer(layer) != null) {
198: if (CGraphicsQ.DEBUG) {
199: System.err.println("Relocate Layer: " + layer);
200: }
201: int[] oldBounds = { layer.bounds[X],
202: layer.bounds[Y], layer.bounds[W],
203: layer.bounds[H] };
204:
205: if (oldBounds[X] != x || oldBounds[Y] != y
206: || oldBounds[W] != w || oldBounds[H] != h) {
207: layer.setBounds(x, y, w, h);
208: layer.addDirtyRegion();
209: requestRepaint();
210: layer.relocateNotify(oldBounds);
211: return true;
212: }
213: }
214: }
215: }
216: return false;
217: }
218:
219: /**
220: * Allow this window to process key input. The type of key input
221: * will be press, release, repeat, etc. The key code will identify
222: * which key generated the event. This method will return true if
223: * the event was processed by this window or one of its layers,
224: * false otherwise.
225: *
226: * @param type the type of key event (press, release, repeat)
227: * @param keyCode the identifier of the key which generated the event
228: * @return true if this window or one of its layers processed the event
229: */
230: public boolean keyInput(int type, int keyCode) {
231: CLayer layer;
232: synchronized (layers) {
233: for (CLayerElement le = layers.getTop(); le != null; le = le
234: .getLower()) {
235: layer = le.getLayer();
236: if (layer.supportsInput
237: && layer.keyInput(type, keyCode)) {
238: return true;
239: }
240: }
241: } // sync
242: return false;
243: }
244:
245: /**
246: * Allow this window to process pointer input. The type of pointer input
247: * will be press, release, drag, etc. The x and y coordinates will
248: * identify the point at which the pointer event occurred in the coordinate
249: * system of this window. This window will translate the coordinates
250: * appropriately for each layer contained in this window. This method will
251: * return true if the event was processed by this window or one of its
252: * layers, false otherwise.
253: *
254: * @param type the type of pointer event (press, release, drag)
255: * @param x the x coordinate of the location of the event
256: * @param y the y coordinate of the location of the event
257: * @return true if this window or one of its layers processed the event
258: */
259: public boolean pointerInput(int type, int x, int y) {
260: CLayer layer;
261: synchronized (layers) {
262: for (CLayerElement le = layers.getTop(); le != null; le = le
263: .getLower()) {
264: layer = le.getLayer();
265: if (layer.visible && layer.supportsInput
266: && layer.handlePoint(x, y)) {
267: // If the layer is visible, supports input, and
268: // contains the point of the pointer press, we translate
269: // the point into the layer's coordinate space and
270: // pass on the input
271: if (layer.pointerInput(type, x - layer.bounds[X], y
272: - layer.bounds[Y])) {
273: return true;
274: }
275: }
276: }
277: } // sync
278: return false;
279: }
280:
281: /**
282: * Handle input from some type of device-dependent
283: * input method. This could be input from something
284: * such as T9, or a phonebook lookup, etc.
285: *
286: * @param str the text to handle as direct input
287: * @return true if this window or one of its layers processed the event
288: */
289: public boolean methodInput(String str) {
290: CLayer layer;
291: synchronized (layers) {
292: for (CLayerElement le = layers.getTop(); le != null; le = le
293: .getLower()) {
294: layer = le.getLayer();
295: if (layer.visible && layer.supportsInput
296: && layer.methodInput(str)) {
297: return true;
298: }
299: }
300: } // sync
301: return false;
302: }
303:
304: /**
305: * Request a repaint. This method MUST be overridden
306: * by subclasses to provide the implementation.
307: */
308: public abstract void requestRepaint();
309:
310: /**
311: * Subtract this layer area from an underlying dirty regions.
312: * The method is designed to reduce dirty regions of a layres
313: * below the opaque visible layer.
314: *
315: * @param le layer list element
316: */
317: private void cleanLowerDirtyRegions(CLayerElement le) {
318: if (CGraphicsQ.DEBUG) {
319: System.err
320: .println("Clean dirty regions under opaque layer: "
321: + le.getLayer());
322: }
323:
324: CLayer l = le.getLayer();
325: for (CLayerElement le2 = le.getLower(); le2 != null; le2 = le2
326: .getLower()) {
327: CLayer l2 = le2.getLayer();
328: if (l2.isDirty()) {
329: l2.subDirtyRegion(l.bounds[X] - l2.bounds[X],
330: l.bounds[Y] - l2.bounds[Y], l.bounds[W],
331: l.bounds[H]);
332: }
333: }
334: }
335:
336: /**
337: * Update dirty regions of all visible layers in the stack regarding
338: * the entire area of the given layer as being dirty. The method is
339: * needed to perform layer move/resize/remove opertion, since other
340: * layers should be informed of changed area.
341: *
342: * @param layer the layer whose area should be reported as dirty to
343: * other stack layers
344: */
345: CLayerElement sweepLayer(CLayer layer) {
346: if (layer != null) {
347: if (CGraphicsQ.DEBUG) {
348: System.err.println("Sweep Layer: " + layer);
349: }
350: synchronized (layers) {
351: CLayerElement le = layers.find(layer);
352: if (le != null) {
353: // IMPL NOTE: when a layer gets removed (or has its setVisible(false))
354: // called, the parent window must loop through all the other
355: // layers and mark them as dirty if they intersect with the
356: // layer being removed (or having its visibility changed).
357: layer.addDirtyRegion();
358: sweepAndMarkDirtyLayer(le, true);
359: return le;
360: }
361: }
362: }
363: return null;
364: }
365:
366: /**
367: * Propagate dirty region of the layer to other layers in the stack.
368: * The method should be called for dirty layers only.
369: * The dirty layer can be invisible in the case it has been
370: * hidden since the previous paint.
371: *
372: * IMPL_NOTE: The layer been removed or set to invisible state since
373: * the previous paint is considered as "hidden", thus it should be
374: * entirely dirty and must affect other visible layers accordingly.
375: *
376: * @param le dirty layer element to be propagated to other layers
377: * @param hidden indicates whether the dirty layer has been hidden
378: * since the previous repaint
379: * @return the highest layer element above le with modified dirty
380: * region, or null if none
381: */
382: private CLayerElement sweepAndMarkDirtyLayer(CLayerElement le,
383: boolean hidden) {
384:
385: if (CGraphicsQ.DEBUG) {
386: System.err.println("Sweep and mark dirty layer: "
387: + le.getLayer());
388: }
389:
390: CLayer l2;
391: CLayerElement res = null;
392: CLayer l = le.getLayer();
393:
394: // Prepare absolute dirty region coordinates of layer l
395: int dx = l.bounds[X];
396: int dy = l.bounds[Y];
397: int dh, dw;
398: if (l.isEmptyDirtyRegions()) {
399: dw = l.bounds[W];
400: dh = l.bounds[H];
401: } else {
402: dx += l.dirtyBounds[X];
403: dy += l.dirtyBounds[Y];
404: dw = l.dirtyBounds[W];
405: dh = l.dirtyBounds[H];
406: }
407:
408: // Sweep dirty region to upper layers
409: for (CLayerElement le2 = le.getUpper(); le2 != null; le2 = le2
410: .getUpper()) {
411:
412: l2 = le2.getLayer();
413: if (l2.visible) {
414: if (l2.addDirtyRegion(dx - l2.bounds[X], dy
415: - l2.bounds[Y], dw, dh)) {
416: // Remember the highest changed layer
417: res = le2;
418: }
419: }
420: }
421:
422: // Sweep non-opaque dirty region to undelying layers
423: if (!l.opaque || hidden) {
424: for (CLayerElement le2 = le.getLower(); le2 != null; le2 = le2
425: .getLower()) {
426:
427: l2 = le2.getLayer();
428: if (l2.visible) {
429: l2.addDirtyRegion(dx - l2.bounds[X], dy
430: - l2.bounds[Y], dw, dh);
431: }
432: }
433:
434: // A newly hidden layer should be dirty only for the first
435: // succeeded paint, it should be cleaned as soon as underlying
436: // layers are properly marked as dirty.
437: if (hidden) {
438: l.cleanDirty();
439: }
440: }
441:
442: return res;
443: }
444:
445: // Heuristic Explanation: Any layer that needs painting also
446: // requires all layers below and above that region to be painted.
447: // This is required because layers may be transparent or even
448: // partially translucent - thus they require that all layers beneath
449: // and above them be repainted as well.
450:
451: // To accomplish this we loop through the stack of layers from the top
452: // most to the bottom most. If a layer is "dirty" (has its dirty bit
453: // set), we find all layers that intersect with the dirty region and
454: // we mark that layer to be painted as well. If that layer is already
455: // marked to be painted and has its own dirty region, we union
456: // the existing region with the new region. If that layer does
457: // not have a dirty region, we simply set a new one. In the case a
458: // dirty region is modified for a higher layer been processed already
459: // we need to restart the loop from the modified layer.
460:
461: // After doing this initial iteration, all layers will now be
462: // marked dirty where appropriate and have their individual dirty
463: // regions set. We then make another iteration from the bottom
464: // most layer to the top, painting the dirty region of each layer.
465:
466: /**
467: * First Pass: We do sweep and mark of all layers requiring a repaint,
468: * the areas behind a visible opaque layers need no repaint
469: */
470: private void sweepAndMarkLayers() {
471: if (CGraphicsQ.DEBUG) {
472: System.err.println("[Sweep and mark layers]");
473: }
474:
475: CLayer l;
476: CLayerElement changed;
477: CLayerElement le = layers.getTop();
478:
479: while (le != null) {
480: l = le.getLayer();
481:
482: if (l.visible && l.opaque) {
483: cleanLowerDirtyRegions(le);
484: }
485:
486: // The dirty layer can be invisible, that means it
487: // has been hidden since the previous paint.
488: if (l.isDirty()) {
489: // In the case higher layer was changed we need to
490: // restart all the algorithm from the changed layer
491: changed = sweepAndMarkDirtyLayer(le, !l.visible);
492: if (changed != null) {
493: if (CGraphicsQ.DEBUG) {
494: System.err.println("Restart sweep and mark: "
495: + changed.getLayer());
496: }
497: le = changed;
498: changed = null;
499: continue;
500: }
501: }
502: // Go to next lower layer
503: le = le.getLower();
504: }
505: }
506:
507: /**
508: * Copy dirty layer references to array for further painting.
509: * The copying is needed to not keep lock on layers list when
510: * layers painting will happen.
511: */
512: private void copyDirtyLayers() {
513: if (CGraphicsQ.DEBUG) {
514: System.err.println("[Copy dirty layers]");
515: }
516: CLayer l;
517: dirtyCount = 0;
518: int layersCount = layers.size();
519: // Heuristics to increase array for copied dirty layers
520: if (layersCount > dirtyMaxCount) {
521: dirtyMaxCount += layersCount;
522: dirtyLayers = new CLayer[dirtyMaxCount];
523: }
524: // Copy dirty layer references
525: for (CLayerElement le = layers.getBottom(); le != null; le = le
526: .getUpper()) {
527: l = le.getLayer();
528: if (l.visible && l.isDirty()) {
529: l.copyLayerBounds();
530: dirtyLayers[dirtyCount++] = l;
531:
532: } else { // !(visible && dirty)
533: if (CGraphicsQ.DEBUG) {
534: System.err.println("Skip Layer: " + l);
535: }
536: } // if
537: } // for
538:
539: }
540:
541: /**
542: * Second Pass: We sweep through the layers from the bottom to
543: * the top and paint each one that is marked as dirty
544: *
545: * Note, that the painting for copied layers is done here to
546: * not hold the layers lock during the painting.
547: *
548: * @param g The graphics object to use to paint this window.
549: * @param refreshQ The custom queue which holds the set of refresh
550: * regions needing to be blitted to the screen
551: */
552: private void paintLayers(Graphics g, CGraphicsQ refreshQ) {
553: if (CGraphicsQ.DEBUG) {
554: System.err.println("[Paint dirty layers]");
555: }
556:
557: for (int i = 0; i < dirtyCount; i++) {
558: CLayer l = dirtyLayers[i];
559:
560: // Prepare relative dirty region coordinates
561: // of the current layer
562: int dx = l.dirtyBoundsCopy[X];
563: int dy = l.dirtyBoundsCopy[Y];
564: int dw = l.dirtyBoundsCopy[W];
565: int dh = l.dirtyBoundsCopy[H];
566:
567: // Before we call into the layer to paint, we
568: // translate the graphics context into the layer's
569: // coordinate space
570: g.translate(l.boundsCopy[X], l.boundsCopy[Y]);
571:
572: if (CGraphicsQ.DEBUG) {
573: System.err.println("Painting Layer: " + l);
574: System.err.println("\tClip: " + dx + ", " + dy + ", "
575: + dw + ", " + dh);
576: }
577:
578: // Clip the graphics to only contain the dirty region of
579: // the layer (if the dirty region isn't set, clip to the
580: // whole layer contents).
581: g.clipRect(dx, dy, dw, dh);
582: refreshQ.queueRefresh(l.boundsCopy[X] + dx, l.boundsCopy[Y]
583: + dy, dw, dh);
584: l.paint(g);
585:
586: // We restore our graphics context to prepare
587: // for the next layer
588: g.translate(-g.getTranslateX(), -g.getTranslateY());
589: g.translate(tranX, tranY);
590:
591: // We reset our clip to this window's bounds again.
592: g.setClip(bounds[X], bounds[Y], bounds[W], bounds[H]);
593:
594: g.setFont(font);
595: g.setColor(color);
596: } // for
597: }
598:
599: /**
600: * Sets all visible layers to dirty state.
601: * The method is needed on system events like screen rotation,
602: * when generic layers system is not capabel to properly analyze
603: * layers changes, e.g. of move/resize kind. It could be fixed in
604: * the future and this method will be out of use.
605: */
606: public void setAllDirty() {
607: synchronized (layers) {
608: CLayer l;
609: for (CLayerElement le = layers.getBottom(); le != null; le = le
610: .getUpper()) {
611: l = le.getLayer();
612: if (l.visible) {
613: l.addDirtyRegion();
614: } // if
615: } // for
616: } // synchronized
617: }
618:
619: /**
620: * Paint this window. This method should not generally be overridden by
621: * subclasses. This method carefully stores the clip, translation, and
622: * color before calling into subclasses. The graphics context should be
623: * translated such that it is in this window's coordinate space (0,0 is
624: * the top left corner of this window).
625: *
626: * @param g The graphics object to use to paint this window.
627: * @param refreshQ The custom queue which holds the set of refresh
628: * regions needing to be blitted to the screen
629: */
630: public void paint(Graphics g, CGraphicsQ refreshQ) {
631: // We reset our dirty flag first. Any layers that become
632: // dirty in the duration of this method will then cause it
633: // to toggle back to true for the subsequent pass.
634: // IMPL NOTE: when layers start to do complex animation, there will
635: // likely need to be better atomic handling of the dirty state,
636: // and layers becoming dirty and getting painted
637: this .dirty = false;
638:
639: // Store the clip, translate, font, color
640: cX = g.getClipX();
641: cY = g.getClipY();
642: cW = g.getClipWidth();
643: cH = g.getClipHeight();
644:
645: tranX = g.getTranslateX();
646: tranY = g.getTranslateY();
647:
648: font = g.getFont();
649: color = g.getColor();
650:
651: // We set the basic clip to the size of this window
652: g.setClip(bounds[X], bounds[Y], bounds[W], bounds[H]);
653:
654: synchronized (layers) {
655: sweepAndMarkLayers();
656: copyDirtyLayers();
657: }
658: paintLayers(g, refreshQ);
659:
660: // We restore the original clip. The original font, color, etc.
661: // have already been restored
662: g.setClip(cX, cY, cW, cH);
663: }
664:
665: /**
666: * Establish a background. This method will evaluate the parameters
667: * and create a background which is appropriate. If the image is non-null,
668: * the image will be used to create the background. If the image is null,
669: * the values for the colors will be used and the background will be
670: * painted in fill color instead. If the image is null, and the background
671: * color is a negative value, this layer will become transparent and no
672: * background will be painted.
673: *
674: * @param bgImage the image to use for the background tile (or null)
675: * @param bgColor if the image is null, use this color as a background
676: * fill color
677: */
678: synchronized void setBackground(Image bgImage, int bgColor) {
679: bgLayer.setBackground(bgImage, bgColor);
680: }
681:
682: /**
683: * Returns true if any layer of this window is in need of repainting.
684: *
685: * @return true if any layer of this window is marked as 'dirty'
686: * and needs repainting.
687: */
688: public boolean isDirty() {
689: return this .dirty;
690: }
691:
692: /**
693: * Mark this window as being dirty and requiring a repaint.
694: */
695: public void setDirty() {
696: this .dirty = true;
697: }
698:
699: /** Constant used to reference the '0' index of the bounds array */
700: public static final int X = 0;
701:
702: /** Constant used to reference the '1' index of the bounds array */
703: public static final int Y = 1;
704:
705: /** Constant used to reference the '2' index of the bounds array */
706: public static final int W = 2;
707:
708: /** Constant used to reference the '3' index of the bounds array */
709: public static final int H = 3;
710: }
|