/*
* SVGMapScreen.java
*
* Copyright � 1998-2011 Research In Motion Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Note: For the sake of simplicity, this sample application may not leverage
* resource bundles and resource strings. However, it is STRONGLY recommended
* that application developers make use of the localization features available
* within the BlackBerry development platform to ensure a seamless application
* experience across a variety of languages and geographies. For more information
* on localizing your application, please refer to the BlackBerry Java Development
* Environment Development Guide associated with this release.
*/
package com.rim.samples.device.svg.svgmapdemo;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
import javax.microedition.m2g.SVGAnimator;
import javax.microedition.m2g.SVGImage;
import javax.microedition.m2g.ScalableImage;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.Manager;
import net.rim.device.api.ui.Screen;
import net.rim.device.api.ui.TouchEvent;
import net.rim.device.api.ui.TouchGesture;
import net.rim.device.api.ui.component.CheckboxField;
import net.rim.device.api.ui.component.Menu;
import net.rim.device.api.ui.container.FlowFieldManager;
import net.rim.device.api.ui.container.MainScreen;
import org.w3c.dom.Document;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.svg.SVGElement;
import org.w3c.dom.svg.SVGRect;
import org.w3c.dom.svg.SVGSVGElement;
/**
* This is a custom screen where an interactive svg map would be used in
* conjuction with cldc checkboxes to interact with the svg model.
*/
public final class SVGMapScreen extends MainScreen implements
FieldChangeListener, EventListener {
// Actual SVG map width and height
private static final int SVG_WIDTH = 1024;
private static final int SVG_HEIGHT = 768;
// Display/Map width and height
private static final int MAP_DISPLAY_WIDTH = 480;
private static final int MAP_DISPLAY_HEIGHT = 240;
// Desired scroll movement
private static final int SCROLL_CHANGE = 15;
// For touch screen
private static final int SCROLL_FACTOR = 3;
// SVGImage instance to store the parsed SVG data.
private SVGImage _image;
// SVGAnimator to obtain an SVGField.
private SVGAnimator _animator;
// Document to hold SVGImage contents.
private Document _document;
// Field to hold the animated svg context.
private Field _svgField;
// Manager to lay out the SVG Map item.
private SVGMapManager _mapManager;
// HashTable to store the checkboxes to their svg elements.
private Hashtable _checkboxes;
// Manager to lay out checkboxes.
private FlowFieldManager _checkboxManager;
// The svg root element.
private SVGSVGElement _svg;
// the svg viewbox.
private SVGRect _svgViewBox;
private SVGElement _viewportElement;
// Current display position of the SVG map.
private int _positionX = 0;
private int _positionY = 0;
/**
* Constructor.
*/
public SVGMapScreen() {
setTitle("SVG Map Demo");
try {
// Obtains an input stream to the SVG file URL.
final InputStream is =
getClass().getResourceAsStream("/sample.svg");
// Loads the SVG image using the input stream connection.
_image = (SVGImage) ScalableImage.createImage(is, null);
// Obtain the images document.
_document = _image.getDocument();
// Create an interactive SVG animator that hosts SVG field.
_animator =
SVGAnimator.createAnimator(_image,
"net.rim.device.api.ui.Field");
// Initialize our screens user interface.
initializeUI();
// There may be timed interactive content here so start the
// animator.
_animator.play();
} catch (final IOException ex) {
System.exit(1);
}
}
/**
* @see Screen#touchEvent(TouchEvent)
*/
protected boolean touchEvent(final TouchEvent message) {
final TouchGesture touchGesture = message.getGesture();
if (touchGesture != null) {
if (_mapManager.isPanning()) {
// If the user has performed a swipe gesture within the map area
// we will move the map accordingly.
if (touchGesture.getEvent() == TouchGesture.SWIPE
&& message.getY(1) < 240) {
int horizontal = 0;
int vertical = 0;
// Retrieve the swipe magnitude so we know how
// far to move the map.
final int magnitude = touchGesture.getSwipeMagnitude();
// Move the map in the direction of the swipe.
switch (touchGesture.getSwipeDirection()) {
case TouchGesture.SWIPE_NORTH:
vertical = magnitude / SCROLL_FACTOR;
// vertical = SCROLL_CHANGE;
break;
case TouchGesture.SWIPE_SOUTH:
vertical = -(magnitude / SCROLL_FACTOR);
// vertical = -(SCROLL_CHANGE * SCROLL_FACTOR);
break;
case TouchGesture.SWIPE_EAST:
horizontal = -(magnitude / SCROLL_FACTOR);
// horizontal = -(SCROLL_CHANGE);
break;
case TouchGesture.SWIPE_WEST:
horizontal = magnitude / SCROLL_FACTOR;
// horizontal = -SCROLL_CHANGE;
break;
}
update(horizontal, vertical);
}
}
// Performing a double tap gesture on the map will toggle panning
// mode
if (touchGesture.getEvent() == TouchGesture.DOUBLE_TAP
&& message.getY(1) < 240) {
togglePanMode();
}
}
return super.touchEvent(message);
}
/**
* Initialize the SVG Map and the remaining screen components.
*/
private void initializeUI() {
// Initialize a FlowFieldManager to have the checkboxes layed out.
_checkboxManager = new FlowFieldManager(Manager.HORIZONTAL_SCROLL);
// Add the SVG Map to a custom manager to handle its behaviour as a
// custom
// togglable field.
_mapManager = new SVGMapManager();
add(_mapManager);
_svgField = (Field) _animator.getTargetComponent();
_mapManager.add(_svgField);
// Initialize a hashtable for our check boxes.
_checkboxes = new Hashtable(3);
// Populates the hashtable, associates listeners with the checkbox
final CheckboxField roadsCheckBox = new CheckboxField("Roads ", true);
final SVGElement roadsGroup =
(SVGElement) _document.getElementById("roads");
_checkboxes.put(roadsCheckBox, roadsGroup);
final CheckboxField railsCheckBox =
new CheckboxField("Railways ", true);
final SVGElement railsGroup =
(SVGElement) _document.getElementById("railways");
_checkboxes.put(railsCheckBox, railsGroup);
final CheckboxField interestsCheckBox =
new CheckboxField("Points of interest ", true);
final SVGElement interestsGroup =
(SVGElement) _document.getElementById("interests");
_checkboxes.put(interestsCheckBox, interestsGroup);
final CheckboxField restaurantCheckBox =
new CheckboxField("Restaurants ", true);
final SVGElement restaurantGroup =
(SVGElement) _document.getElementById("restaurants");
_checkboxes.put(restaurantCheckBox, restaurantGroup);
// Set this class as the handler of the change listeners.
roadsCheckBox.setChangeListener(this);
railsCheckBox.setChangeListener(this);
restaurantCheckBox.setChangeListener(this);
interestsCheckBox.setChangeListener(this);
// Adds the checkboxes to the layout manager.
_checkboxManager.add(roadsCheckBox);
_checkboxManager.add(railsCheckBox);
_checkboxManager.add(restaurantCheckBox);
_checkboxManager.add(interestsCheckBox);
// Adds the layout manager to the screen.
add(_checkboxManager);
// Obtain the root element and the view box settings.
_svg = (SVGSVGElement) _document.getDocumentElement();
_svgViewBox = _svg.getRectTrait("viewBox");
// Get the border element.
_viewportElement = (SVGElement) _document.getElementById("viewport");
_viewportElement.addEventListener("DOMActivate", this, false);
}
/**
* Toggles the pan mode.
*/
private void togglePanMode() {
_mapManager.togglePanning();
if (_mapManager.isPanning()) {
_animator.invokeLater(new Runnable() {
public void run() {
_viewportElement.setTrait("stroke-width", "12");
_viewportElement.setTrait("stroke", "red");
}
});
} else {
_animator.invokeLater(new Runnable() {
public void run() {
_viewportElement.setTrait("stroke-width", " 7");
_viewportElement.setTrait("stroke", "black");
}
});
}
}
/**
* Handles the DOMActivate event. Locks the control to the SVG element if
* SVG map field is selected for panning.
* <p>
*
* @param evt
* rg.w3c.dom.events.Event.
*/
public void handleEvent(final Event evt) {
if (evt.getCurrentTarget() == _viewportElement) {
togglePanMode();
}
}
/**
* Updates current size of the Rectangle and position and value of text
* fields.
* <p>
*
* @param horizontal
* - viewport and border width update.
* @param vertical
* - viewport and border height update.
*/
private void update(final int horizontal, final int vertical) {
// Calculate the new position of the map.
_positionX += horizontal;
_positionY += vertical;
// Update the new position of the map only if new panning
// position is within the SVG map space.
if (_positionX <= 0) {
_positionX = 0;
}
if (_positionX >= SVG_WIDTH - MAP_DISPLAY_WIDTH) {
_positionX = SVG_WIDTH - MAP_DISPLAY_WIDTH;
}
if (_positionY <= 0) {
_positionY = 0;
}
if (_positionY >= SVG_HEIGHT - MAP_DISPLAY_HEIGHT) {
_positionY = SVG_HEIGHT - MAP_DISPLAY_HEIGHT;
}
// Update the viewbox transformation and border.
_animator.invokeLater(new Runnable() {
public void run() {
// Update the viewbox co-ordinates.
_svgViewBox.setX(_positionX);
_svgViewBox.setY(_positionY);
_svgViewBox.setWidth(MAP_DISPLAY_WIDTH);
_svgViewBox.setHeight(MAP_DISPLAY_HEIGHT);
_svg.setRectTrait("viewBox", _svgViewBox);
// Update the viewport
_viewportElement.setFloatTrait("x", _positionX);
_viewportElement.setFloatTrait("y", _positionY);
}
});
}
/**
* @see Screen#navigationMovement(int, int, int, int)
*/
protected boolean navigationMovement(final int dx, final int dy,
final int status, final int time) {
if (_mapManager.isPanning()) {
int horizontal = 0;
int vertical = 0;
if (dx < 0) {
horizontal = -SCROLL_CHANGE;
} else if (dx > 0) {
horizontal = SCROLL_CHANGE;
}
if (dy < 0) {
vertical = -SCROLL_CHANGE;
} else if (dy > 0) {
vertical = SCROLL_CHANGE;
}
update(horizontal, vertical);
return true;
}
return super.navigationMovement(dx, dy, status, time);
}
/**
* @see Screen#onMenu (int)
*/
public boolean onMenu(final int instance) {
if (instance == Menu.INSTANCE_CONTEXT) {
return true;
} else {
return super.onMenu(instance);
}
}
/**
* @see FieldChangeListener#fieldChanged(Field, int)
*/
public void fieldChanged(final Field field, final int context) {
final CheckboxField cbf = (CheckboxField) field;
final SVGElement svgElement = (SVGElement) _checkboxes.get(cbf);
// If the checkbox is checked change svg display trait.
if (cbf.getChecked() == true) {
_animator.invokeLater(new Runnable() {
public void run() {
svgElement.setTrait("display", "inline");
}
});
} else {
_animator.invokeLater(new Runnable() {
public void run() {
svgElement.setTrait("display", "none");
}
});
}
}
/**
* Override this method to prevent save dialog from being displayed.
*
* @see net.rim.device.api.ui.Screen#onClose()
*/
public boolean onClose() {
close();
return true;
}
}