001: /*
002: *
003: * Copyright (c) 2007, Sun Microsystems, Inc.
004: *
005: * All rights reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * * Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: * * Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in the
015: * documentation and/or other materials provided with the distribution.
016: * * Neither the name of Sun Microsystems nor the names of its contributors
017: * may be used to endorse or promote products derived from this software
018: * without specific prior written permission.
019: *
020: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
023: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031: */
032: package examples.cityguide;
033:
034: import java.io.IOException;
035:
036: import java.util.Timer;
037: import java.util.TimerTask;
038:
039: import javax.microedition.lcdui.*;
040: import javax.microedition.location.Landmark;
041:
042: /**
043: * This class represents a "view" of a city map. It shows a visitor and
044: * landmarks.
045: *
046: * @version 1.3
047: */
048: public class MapCanvas extends Canvas implements MapListener {
049: private static final int X = 0;
050: private static final int Y = 1;
051: private static final int LANDMARK_INFO_HEIGHT = Font
052: .getDefaultFont().getHeight() + 10;
053: private CityMap cityMap;
054: private MapLandmark[] mapLandmarks;
055: private int[] tmpXY;
056: private Timer timer;
057: private MapLandmark lastActivated;
058:
059: /** The actual visitor position. */
060: private int visitorX;
061: private int visitorY;
062:
063: /** The last displayed visitor position. */
064: private int oldVisitorX;
065: private int oldVisitorY;
066: private Image[] anim;
067: private Image logoImage;
068:
069: /** The phase in which is the animation of active landmarks. */
070: private int phase = 0;
071:
072: /**
073: * Creates a new instance of MapCanvas. Register itself as a map listener
074: * to the given instance of the CityMap class.
075: */
076: public MapCanvas(CityMap cityMap) {
077: this .cityMap = cityMap;
078: this .mapLandmarks = cityMap.getMapLandmarks();
079:
080: tmpXY = new int[2];
081:
082: ImageManager imageManager = ImageManager.getInstance();
083:
084: anim = new Image[8];
085:
086: for (int i = 0; i < anim.length; ++i) {
087: anim[i] = imageManager.getImage("anim" + (i + 1));
088: }
089:
090: logoImage = imageManager.getImage("logo");
091:
092: cityMap.getVisitorXY(tmpXY);
093: visitorX = tmpXY[CityMap.X];
094: visitorY = tmpXY[CityMap.Y];
095:
096: cityMap.addMapListener(this );
097: }
098:
099: /**
100: * Updates the animation of active landmarks.
101: */
102: synchronized void updateActivatedLandmarks() {
103: calculateViewportOffset(tmpXY, visitorX, visitorY);
104:
105: int offsetX = tmpXY[X];
106: int offsetY = tmpXY[Y];
107:
108: for (int i = 0; i < mapLandmarks.length; ++i) {
109: if (mapLandmarks[i].isActive()) {
110: int x0 = mapLandmarks[i].getX() - 16 - offsetX;
111: int y0 = mapLandmarks[i].getY() - 16 - offsetY;
112: repaint(x0, y0, 32, 32);
113: }
114: }
115: }
116:
117: /**
118: * Calculates the viewport offset of the map according to the given
119: * visitor xy coordinates. It tries to center the visitor on the screen.
120: */
121: private void calculateViewportOffset(int[] xy, int visitorX,
122: int visitorY) {
123: Image mapImage = cityMap.getMapImage();
124:
125: int mapWidth = mapImage.getWidth();
126: int mapHeight = mapImage.getHeight();
127:
128: int vpWidth = getWidth();
129: int vpHeight = getHeight();
130:
131: int x = visitorX - (vpWidth / 2);
132: int y = visitorY - (vpHeight / 2);
133:
134: if ((x + vpWidth) > mapWidth) {
135: x = mapWidth - vpWidth;
136: }
137:
138: if ((y + vpHeight) > mapHeight) {
139: y = mapHeight - vpHeight;
140: }
141:
142: if (x < 0) {
143: x = 0;
144: }
145:
146: if (y < 0) {
147: y = 0;
148: }
149:
150: xy[X] = x;
151: xy[Y] = y;
152: }
153:
154: public void paint(Graphics g) {
155: int visitorX;
156: int visitorY;
157: MapLandmark[] mapLandmarks;
158:
159: synchronized (this ) {
160: visitorX = this .visitorX;
161: visitorY = this .visitorY;
162: mapLandmarks = this .mapLandmarks;
163: }
164:
165: Image mapImage = cityMap.getMapImage();
166: ImageManager imageManager = ImageManager.getInstance();
167:
168: calculateViewportOffset(tmpXY, visitorX, visitorY);
169:
170: int translateX = -tmpXY[X];
171: int translateY = -tmpXY[Y];
172:
173: if ((visitorX != oldVisitorX) || (visitorY != oldVisitorY)) {
174: g.setClip(0, 0, getWidth(), getHeight());
175: }
176:
177: if (lastActivated != null) {
178: // show the name of the last activated landmark
179: paintLandmarkInfo(g, lastActivated.getLandmark());
180: g.clipRect(0, LANDMARK_INFO_HEIGHT, getWidth(), getHeight()
181: - LANDMARK_INFO_HEIGHT);
182: }
183:
184: g.translate(translateX, translateY);
185:
186: // draw the map
187: g.drawImage(mapImage, 0, 0, Graphics.LEFT | Graphics.TOP);
188:
189: int clipX0 = g.getClipX();
190: int clipY0 = g.getClipY();
191: int clipX1 = clipX0 + g.getClipWidth();
192: int clipY1 = clipY0 + g.getClipHeight();
193:
194: // draw the images of the landmarks
195: for (int i = 0; i < mapLandmarks.length; ++i) {
196: int x0 = mapLandmarks[i].getX() - 16;
197: int y0 = mapLandmarks[i].getY() - 16;
198: int x1 = x0 + 32;
199: int y1 = y0 + 32;
200:
201: if ((x1 >= clipX0) && (x0 < clipX1) && (y1 >= clipY0)
202: && (y0 < clipY1)) {
203: g.drawImage(mapLandmarks[i].getImage(), mapLandmarks[i]
204: .getX(), mapLandmarks[i].getY(),
205: Graphics.HCENTER | Graphics.VCENTER);
206: }
207: }
208:
209: // draw the visitor icon
210: g.drawImage(cityMap.getVisitorImage(), visitorX, visitorY,
211: Graphics.HCENTER | Graphics.VCENTER);
212:
213: // show the animation on the top of activate landmarks
214: for (int i = 0; i < mapLandmarks.length; ++i) {
215: if (mapLandmarks[i].isActive()) {
216: int x0 = mapLandmarks[i].getX() - 16;
217: int y0 = mapLandmarks[i].getY() - 16;
218: g.drawImage(anim[phase], x0, y0, Graphics.LEFT
219: | Graphics.TOP);
220: }
221: }
222:
223: g.translate(-translateX, -translateY);
224:
225: // draw the logo
226: int x0 = getWidth() - logoImage.getWidth();
227: int y0 = getHeight() - logoImage.getHeight();
228: g.drawImage(logoImage, getWidth(), getHeight(), Graphics.BOTTOM
229: | Graphics.RIGHT);
230: g.drawRect(x0, y0, logoImage.getWidth() - 1, logoImage
231: .getHeight() - 1);
232:
233: oldVisitorX = visitorX;
234: oldVisitorY = visitorY;
235: }
236:
237: /**
238: * Draws the name of the given landmark in the blue rectangle with the black
239: * border.
240: */
241: private void paintLandmarkInfo(Graphics g, Landmark l) {
242: int oldColor = g.getColor();
243: int width = getWidth();
244: g.setColor(0, 0, 0);
245: g.drawRect(0, 0, width - 1, LANDMARK_INFO_HEIGHT - 1);
246: g.setColor(0x40, 0x40, 0x80);
247: g.fillRect(1, 1, width - 2, LANDMARK_INFO_HEIGHT - 2);
248: g.setColor(0xff, 0xff, 0xff);
249: g.drawString(l.getName(), 4, 4, Graphics.LEFT | Graphics.TOP);
250: g.setColor(oldColor);
251: }
252:
253: /**
254: * A method which is called by the city map, when the visitor changes his
255: * position.
256: */
257: public synchronized void visitorPositionChanged(CityMap sender) {
258: cityMap.getVisitorXY(tmpXY);
259: visitorX = tmpXY[CityMap.X];
260: visitorY = tmpXY[CityMap.Y];
261: repaint();
262: }
263:
264: /**
265: * A method which is called by the city map, when the visitor changes his
266: * state.
267: */
268: public void visitorStateChanged(CityMap sender) {
269: repaint();
270: }
271:
272: /**
273: * A method which is called by the city map, when a landmark gets activated
274: * or deactivated.
275: */
276: public synchronized void landmarkStateChanged(CityMap sender,
277: MapLandmark mapLandmark) {
278: if (mapLandmark.isActive()) {
279: lastActivated = mapLandmark;
280: repaint();
281: } else {
282: calculateViewportOffset(tmpXY, visitorX, visitorY);
283:
284: int offsetX = tmpXY[X];
285: int offsetY = tmpXY[Y];
286: int x0 = mapLandmark.getX() - 16 - offsetX;
287: int y0 = mapLandmark.getY() - 16 - offsetY;
288: repaint(x0, y0, 32, 32);
289:
290: if (lastActivated == mapLandmark) {
291: lastActivated = null;
292: repaint();
293: }
294: }
295: }
296:
297: /**
298: * A method which is called by the city map, when the whole set of landmarks
299: * changes.
300: */
301: public synchronized void landmarksChanged(CityMap sender) {
302: mapLandmarks = cityMap.getMapLandmarks();
303: lastActivated = null;
304: repaint();
305: }
306:
307: /**
308: * Starts an animation timer when the canvas gets visible and registers the
309: * canvas instance to get the notifications from the CityMap instance.
310: */
311: protected void showNotify() {
312: if (timer != null) {
313: timer.cancel();
314: }
315:
316: timer = new Timer();
317: timer.schedule(new updateTask(), 500, 500);
318: cityMap.addMapListener(this );
319: }
320:
321: /**
322: * Stops the animation timer when the canvas gets invisible and unregisters
323: * itself from receiving the notifications from the CityMap instance.
324: */
325: protected void hideNotify() {
326: if (timer != null) {
327: timer.cancel();
328: timer = null;
329: }
330:
331: cityMap.removeMapListener(this );
332: }
333:
334: /** The final unregistration. */
335: public void cleanup() {
336: if (timer != null) {
337: timer.cancel();
338: timer = null;
339: }
340:
341: cityMap.removeMapListener(this );
342: }
343:
344: /**
345: * This task is executed periodically to update animated parts of the
346: * map.
347: */
348: private class updateTask extends TimerTask {
349: public void run() {
350: phase = (phase + 1) % anim.length;
351: updateActivatedLandmarks();
352: }
353: }
354: }
|