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 com.sun.perseus.demo.picturedecorator;
033:
034: import java.util.Vector;
035:
036: import javax.microedition.lcdui.Canvas;
037: import javax.microedition.lcdui.Display;
038: import javax.microedition.lcdui.Graphics;
039: import javax.microedition.lcdui.Image;
040: import javax.microedition.m2g.SVGImage;
041: import javax.microedition.m2g.ScalableGraphics;
042:
043: import org.w3c.dom.Document;
044: import org.w3c.dom.svg.SVGElement;
045: import org.w3c.dom.svg.SVGLocatableElement;
046: import org.w3c.dom.svg.SVGRect;
047: import org.w3c.dom.svg.SVGSVGElement;
048:
049: /**
050: * The <code>ItemPicker</code> canvas creates thumbnails of the various props
051: * from its associated props images and displays them in a grid.
052: * The user can use the arrow keys to select a thumbnail (which gets highlighted)
053: * and use the fire button to select the desired item or the '*' key to cancel
054: * the picker.
055: */
056: public class ItemPicker extends Canvas {
057: /**
058: * The SVG Namespace URI.
059: */
060: public static final String SVG_NAMESPACE_URI = "http://www.w3.org/2000/svg";
061:
062: /**
063: * The XLink Namespace URI.
064: */
065: public static final String XLINK_NAMESPACE_URI = "http://www.w3.org/1999/xlink";
066:
067: /**
068: * Desired thumbnail width
069: */
070: public static final int THUMBNAIL_WIDTH = 60;
071:
072: /**
073: * Desired thumbnail height
074: */
075: public static final int THUMBNAIL_HEIGHT = 60;
076:
077: /**
078: * The desired padding for each thumbnail
079: */
080: public static final int THUMBNAIL_PADDING = 5;
081:
082: /**
083: * Offscreen where the thumbnails are rendered.
084: */
085: private Image offscreen;
086:
087: /**
088: * Selected row
089: */
090: private int selectedRow;
091:
092: /**
093: * Selected col
094: */
095: private int selectedCol;
096:
097: /**
098: * Number of rows
099: */
100: private int nr;
101:
102: /**
103: * Number of columns
104: */
105: private int nc;
106:
107: /**
108: * The highest number of rows.
109: */
110: private int maxR;
111:
112: /**
113: * The highest number of columns
114: */
115: private int maxC;
116:
117: /**
118: * The grid's top margin
119: */
120: private int topMargin;
121:
122: /**
123: * The grid's left margin
124: */
125: private int leftMargin;
126:
127: /**
128: * The display to use
129: */
130: private Display display;
131:
132: /**
133: * The associated PhotoFrame instance where the image is decorated
134: * with items selected in this picker.
135: */
136: private PhotoFrame frame;
137:
138: /**
139: * ScalableGraphics used to render the SVG image
140: */
141: private ScalableGraphics sg;
142:
143: /**
144: * The use element used to render items.
145: */
146: private SVGLocatableElement use;
147:
148: /**
149: * The root svg element.
150: */
151: private SVGSVGElement svg;
152:
153: /**
154: * True when the user is in the process of selecting an item.
155: */
156: private boolean selecting;
157:
158: /**
159: * The list of displayed items.
160: */
161: private Vector items;
162:
163: /**
164: * The SVGImage where the items are defined.
165: */
166: private SVGImage svgImage;
167:
168: /**
169: * @param frame the associated PhotoFrame instance.
170: * @param svgImage the svgImage where items are defined.
171: * @param svgImage the SVGImage containing all the props definitions.
172: */
173: public ItemPicker(final PhotoFrame frame, final SVGImage svgImage,
174: final Display display) {
175: if ((svgImage == null) || (frame == null) || (display == null)) {
176: throw new NullPointerException();
177: }
178:
179: this .display = display;
180: this .frame = frame;
181: this .svgImage = svgImage;
182:
183: // Save the original viewport width / height
184: int originalWidth = svgImage.getViewportWidth();
185: int originalHeight = svgImage.getViewportHeight();
186:
187: // Prepare an offscreen raster
188: offscreen = Image.createImage(getWidth(), getHeight());
189:
190: Graphics g = offscreen.getGraphics();
191: sg = ScalableGraphics.createInstance();
192:
193: // We will render the SVG image in thumbnails
194: svgImage.setViewportWidth(THUMBNAIL_WIDTH);
195: svgImage.setViewportHeight(THUMBNAIL_HEIGHT);
196:
197: // Create an offscreen buffer where we will draw the different
198: // items in a grid-like layout.
199: int width = getWidth();
200: int height = getHeight();
201:
202: int pad = THUMBNAIL_PADDING;
203: int iconWidth = THUMBNAIL_WIDTH;
204: int iconHeight = THUMBNAIL_HEIGHT;
205:
206: nc = (width - pad) / (iconWidth + pad);
207: nr = (height - pad) / (iconHeight + pad);
208:
209: topMargin = (height - (nr * iconHeight) - ((nr - 1) * pad)) / 2;
210: leftMargin = (width - (nc * iconWidth) - ((nc - 1) * pad)) / 2;
211:
212: items = new Vector();
213:
214: Document doc = svgImage.getDocument();
215:
216: svg = (SVGSVGElement) svgImage.getDocument()
217: .getDocumentElement();
218: PhotoFrame.locateProps(svg, 0, 0, items, items);
219:
220: int nItems = items.size();
221:
222: // Append a <use> element that we will use for rendering into the
223: // offscreen.
224: use = (SVGLocatableElement) doc.createElementNS(
225: SVG_NAMESPACE_URI, "use");
226: svg.appendChild(use);
227:
228: SVGRect viewBox = svg.getRectTrait("viewBox");
229:
230: // Now, we render up to the maximum number of items.
231: int curRow = 0;
232: int curCol = 0;
233: int tx = leftMargin;
234: int ty = topMargin;
235:
236: for (int ci = 0; ci < nItems; ci++) {
237: SVGElement item = (SVGElement) items.elementAt(ci);
238: use.setTraitNS(XLINK_NAMESPACE_URI, "href", "#"
239: + item.getId());
240:
241: SVGRect bbox = use.getBBox();
242:
243: if (bbox == null) {
244: System.out.println("Bounding Box was null for "
245: + item.getId());
246: }
247:
248: bbox = pad(bbox);
249:
250: svg.setRectTrait("viewBox", bbox);
251:
252: // Render icon
253: sg.bindTarget(g);
254: sg.render(tx, ty, svgImage);
255: sg.releaseTarget();
256:
257: curCol++;
258: tx += iconWidth;
259: tx += pad;
260:
261: if (curCol == nc) {
262: curCol = 0;
263: tx = leftMargin;
264: curRow++;
265: ty += iconHeight;
266: ty += pad;
267:
268: if (curRow == nr) {
269: // We have rendered the maximum number of props.
270: break;
271: }
272: }
273: }
274:
275: use.setTrait("display", "none");
276:
277: maxC = items.size() % nc;
278: maxR = items.size() / nc;
279:
280: if (maxC == 0) {
281: maxC = nc;
282: } else {
283: maxR += 1;
284: }
285:
286: if (items.size() >= (nr * nc)) {
287: maxC = nc;
288: maxR = nr;
289: }
290:
291: // Restore the original viewport width / height
292: svgImage.setViewportWidth(width);
293: svgImage.setViewportHeight(height);
294: svg.setRectTrait("viewBox", viewBox);
295: }
296:
297: public void keyPressed(int keyCode) {
298: int gameAction = getGameAction(keyCode);
299:
300: if (gameAction == RIGHT) {
301: selectedCol++;
302:
303: if (selectedRow < (maxR - 1)) {
304: if (selectedCol == nc) {
305: selectedCol = 0;
306: selectedRow++;
307: }
308: } else {
309: // We are on the last row
310: if (selectedCol == maxC) {
311: selectedCol = 0;
312: selectedRow = 0;
313: }
314: }
315:
316: repaint();
317: serviceRepaints();
318: } else if (gameAction == LEFT) {
319: selectedCol--;
320:
321: if (selectedCol < 0) {
322: if (selectedRow == 0) {
323: selectedCol = maxC - 1;
324: selectedRow = maxR - 1;
325: } else {
326: selectedCol = nc - 1;
327: selectedRow--;
328: }
329: }
330:
331: repaint();
332: serviceRepaints();
333: } else if (gameAction == UP) {
334: selectedRow--;
335:
336: if (selectedRow < 0) {
337: selectedRow = maxR - 1;
338:
339: if (selectedCol >= maxC) {
340: selectedCol = maxC - 1;
341: }
342: }
343:
344: repaint();
345: serviceRepaints();
346: } else if (gameAction == DOWN) {
347: selectedRow++;
348:
349: if (selectedRow == maxR) {
350: selectedRow = 0;
351: } else if (selectedRow == (maxR - 1)) {
352: if (selectedCol >= maxC) {
353: selectedCol = maxC - 1;
354: }
355: }
356:
357: repaint();
358: serviceRepaints();
359: } else if (gameAction == FIRE) {
360: if (selecting) {
361: // The user is confirming his/her choice
362: selecting = false;
363:
364: SVGElement item = (SVGElement) items
365: .elementAt(selectedCol + (selectedRow * nc));
366: frame.addProp(item.getId());
367: display.setCurrent(frame);
368: } else {
369: selecting = true;
370: repaint();
371: serviceRepaints();
372: }
373: } else if (keyCode == KEY_STAR) {
374: if (selecting) {
375: selecting = false;
376: repaint();
377: serviceRepaints();
378: } else {
379: display.setCurrent(frame);
380: }
381: }
382: }
383:
384: /**
385: * Repaints this canvas with the current item selected.
386: *
387: * @param g the Graphics to paint into.
388: */
389: public void paint(final Graphics g) {
390: g.drawImage(offscreen, 0, 0, Graphics.TOP | Graphics.LEFT);
391:
392: // Draw a rectangle over the currently selected item.
393: g.setColor(180, 180, 180);
394:
395: int tx = leftMargin
396: + (selectedCol * (THUMBNAIL_PADDING + THUMBNAIL_WIDTH));
397: int ty = topMargin
398: + (selectedRow * (THUMBNAIL_PADDING + THUMBNAIL_HEIGHT));
399: g.drawRect(tx, ty, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
400:
401: if (selecting) {
402: SVGElement item = (SVGElement) items.elementAt(selectedCol
403: + (selectedRow * nc));
404: use.setTrait("display", "inline");
405: use.setTraitNS(XLINK_NAMESPACE_URI, "href", "#"
406: + item.getId());
407:
408: SVGRect bbox = pad(use.getBBox());
409: SVGRect viewBox = svg.getRectTrait("viewBox");
410: int viewportWidth = svgImage.getViewportWidth();
411: int viewportHeight = svgImage.getViewportHeight();
412: svg.setRectTrait("viewBox", bbox);
413:
414: int w = getWidth();
415:
416: if (w > getHeight()) {
417: w = getHeight();
418: }
419:
420: w = (int) (0.8f * w);
421: svgImage.setViewportWidth(w);
422: svgImage.setViewportHeight(w);
423:
424: g.setColor(255, 255, 255);
425: g.fillRect((getWidth() - w) / 2, (getHeight() - w) / 2, w,
426: w);
427: g.setColor(180, 180, 180);
428: g.drawRect((getWidth() - w) / 2, (getHeight() - w) / 2, w,
429: w);
430:
431: sg.bindTarget(g);
432: sg.render((getWidth() - w) / 2, (getHeight() - w) / 2,
433: svgImage);
434: sg.releaseTarget();
435:
436: svg.setRectTrait("viewBox", viewBox);
437: svgImage.setViewportWidth(viewportWidth);
438: svgImage.setViewportHeight(viewportHeight);
439: use.setTrait("display", "none");
440: }
441: }
442:
443: /**
444: * Helper method. Pads the input bounding box.
445: *
446: * @param bbox the box to pad.
447: */
448: static SVGRect pad(final SVGRect bbox) {
449: float iconPadding = 0.1f; // 10% of the bounding box
450: float hPad = bbox.getWidth() * iconPadding;
451: float vPad = bbox.getHeight() * iconPadding;
452: bbox.setX(bbox.getX() - hPad);
453: bbox.setY(bbox.getY() - vPad);
454: bbox.setWidth(bbox.getWidth() + (2 * hPad));
455: bbox.setHeight(bbox.getHeight() + (2 * vPad));
456:
457: return bbox;
458: }
459: }
|