Thursday, November 13, 2008

Developing mobile applications using Gear episode 3: Managing user inputs

This tutorial will discuss how to handle user inputs in gear based applications and will guide you through the development of a simple picture viewing application.

User input is generally handled by classes inheriting from GWCanvas (thus also GWForm, GWList, GWGrid, etc…). In J2ME applications there are 3 types of inputs available: commands (triggered by soft keys), keypad and touch screen.
In Gear based applications touch screen events can be handled in two different ways: as pointer events and as gesture based events.

Command and gesture triggered events don't need to be handled necessarily in the widgets classes since they use a listener based paradigm, but it is common practice to do so.

To start developing the picture viewer demo we'll have first to create a Gear based J2ME application as explained in the first tutorial "Developing mobile applications using Gear episode 1: Create the first empty application".

The picture viewer demo will permit the user to browse through a set of images included as resources in the JAR archive and to see them in full-screen mode.

Note that the content of this tutorial applies to version 1.1.1 of Gear framework

Step 1: Creating the photo browser widget

First let's create the PhotoBrowser class, make it inherit from GWScrollableList and set it's title and its title icon.

public class PhotoBrowser extends GWScrollableList {
public PhotoBrowser() {
setTitle("Photo browser");
setTitleIcon("/Digitalapes.png");
}
}

Then let's add two commands to the interface (View and Exit).
To do so we have to make PhotoBrowser implement GWCommandListener interface, we have to register it self as a command listener and finally add two commands.

public class PhotoBrowser extends GWScrollableList
implements GWCommandListener {

Command viewCommand = new Command("View",Command.ITEM,0);

public PhotoBrowser() {
setTitle("Photo browser");
setTitleIcon("/Digitalapes.png");
setCommandListener(this);
addCommand(viewCommand);
addCommand(CommonCommands.EXIT);
}

public void commandAction(Command command, GWCanvas sender) {
}
}

Now we need to make the photo browser show the images thumbnail.
This step requires to load the pictures in the photo browser constructor, note that we'll suppose to have a list of the available pictures contained in the PhotoList class (the class code, along with the pictures is included in the demo source code).

public PhotoBrowser() {
setTitle("Photo browser");
setTitleIcon("/Digitalapes.png");
setCommandListener(this);
setStretchIcons(true);
for (int i=0;i < PhotoList.list.length;i++){
ListItem tmp =
new ListItem(PhotoList.list[i],"/"+PhotoList.list[i]);
tmp.setMinVisibleLines(3);
addItem(tmp);
}
addCommand(viewCommand);
addCommand(CommonCommands.EXIT);
}

Now photo browser displays all the thumbnails but no actions are associated to the user inputs.
Let's associate a quit action to the Exit command pressure and a let's make the PhotoView class (that we are going to implement in the next step) displayed when the View command or the fire button are pressed.
At the end your class should look like this one:

public class PhotoBrowser extends GWScrollableList implements
GWCommandListener {

Command viewCommand = new Command("View",Command.ITEM,0);

public PhotoBrowser() {
setTitle("Photo browser");
setTitleIcon("/Digitalapes.png");
setCommandListener(this);
setStretchIcons(true);
for (int i=0;i < PhotoList.list.length;i++){
ListItem tmp =
new ListItem(PhotoList.list[i],"/"+PhotoList.list[i]);

tmp.setMinVisibleLines(3);
addItem(tmp);
}
addCommand(viewCommand);
addCommand(CommonCommands.EXIT);
}

public void itemClicked(ImageItem clickedItem) {
commandAction(viewCommand, this);
}

public void itemSelected(ImageItem selectedItem) {
super.itemSelected(selectedItem);
PhotoList.setCurrentPhoto(getSelectedIndex());
}

public void commandAction(Command command, GWCanvas sender) {
if (command == viewCommand)
EventManager.getInstance().enqueueEvent(
new DisplayWidget(this,SlideShow.class));
else if (command == CommonCommands.EXIT)
EventManager.getInstance().enqueueEvent(new QuitEvent(this));
}

protected boolean showCheck(Display display, EventArg eventArgs) {
setSelectedIndex(PhotoList.getCurrentPhotoIndex());
return true;
}
}

Step 2: Creating the photo viewer widget

As before the first step is creating the class (this time inheriting from GWCanvas), setting title and icon, and managing commands

public class SlideShow extends GWCanvas implements GWCommandListener{

Command browseCommand = new Command("Browse",Command.OK,0);

public SlideShow() {
setTitle("Photo viewer");
setTitleIcon("/Digitalapes.png");
setCommandListener(this);
addCommand(browseCommand);
addCommand(CommonCommands.EXIT);
}

protected void contentPaint(Graphics g, int x, int y, int w, int h) {
}

public void commandAction(Command command, GWCanvas sender) {
if (command == browseCommand)
EventManager.getInstance().enqueueEvent(new
DisplayWidget(this,PhotoBrowser.class));
else if (command == CommonCommands.EXIT)
EventManager.getInstance().enqueueEvent(new QuitEvent(this));
}
}

Now let's handle the current picture paintig: to paint something in the GWCanvasthe paint operations should be performed in the contentPaint() method, using the parameter as paintable area bounds.
In order to make this widget display the picture requested by the PhotoBrowser, we should also put the proper loading code in the showCheck() method.

String currentImage=null;

protected void contentPaint(Graphics g, int x, int y, int w, int h) {
g.drawImage(ImageResourcesBuffer.getImageResource(
currentImage, w-this.formPadding*2, h-this.formPadding*2),
x+w/2, y+h/2, Graphics.VCENTER|Graphics.HCENTER);
}

protected boolean showCheck(Display display, EventArg eventArgs) {
String photoName = PhotoList.getCurrentPhoto();
currentImage = "/"+photoName;
setTitle("Photo view - "+photoName.substring(0, photoName.length()-4));
return true;
}

Now let's associate to the left and right key pressed events a slide action, making the visualize respectively the next and the previous picture

protected void rightArrowPressed() {
PhotoList.getNextPhoto();
EventManager.getInstance().enqueueEvent(new
DisplayWidget(this,SlideShow.class,SlideRight.class));
}

protected void leftArrowPressed() {
PhotoList.getPrevPhoto();
EventManager.getInstance().enqueueEvent(new
DisplayWidget(this,SlideShow.class,SlideLeft.class));
}

The last step for this widget is the handling of touch screen gestures. To handle touch screen gestures we have to make our class implement the GWGestureListener interface, register it as a gesture listener and then implement the required callbacks.
Finally your class should look like this one:

public class SlideShow extends GWCanvas
implements GWCommandListener, GWGestureListener{
Command browseCommand = new Command("Browse",Command.OK,0);
String currentImage=null;

public SlideShow() {
setTitle("Photo viewer");
setTitleIcon("/Digitalapes.png");
setCommandListener(this);
setGestureListener(this);
addCommand(browseCommand);
addCommand(CommonCommands.EXIT);
}

protected void contentPaint(Graphics g, int x, int y, int w, int h) {
g.drawImage(ImageResourcesBuffer.getImageResource(
currentImage, w-this.formPadding*2, h-this.formPadding*2),
x+w/2, y+h/2, Graphics.VCENTER|Graphics.HCENTER);
}

public void reset() {
}

public void commandAction(Command command, GWCanvas sender) {
if (command == browseCommand)
EventManager.getInstance().enqueueEvent(
new DisplayWidget(this,PhotoBrowser.class));
else if (command == CommonCommands.EXIT)
EventManager.getInstance().enqueueEvent(new QuitEvent(this));
}

public void downToUpScrollGesture() {
}

public void upToDownScrollGesture() {
}

public void rightToLeftScrollGesture() {
PhotoList.getNextPhoto();
EventManager.getInstance().enqueueEvent(
new DisplayWidget(this,SlideShow.class,SlideRight.class));
}

public void leftToRightScrollGesture() {
PhotoList.getPrevPhoto();
EventManager.getInstance().enqueueEvent(
new DisplayWidget(this,SlideShow.class,SlideLeft.class));
}

protected void rightArrowPressed() {
PhotoList.getNextPhoto();
EventManager.getInstance().enqueueEvent(new
DisplayWidget(this,SlideShow.class,SlideRight.class));
}

protected void leftArrowPressed() {
PhotoList.getPrevPhoto();
EventManager.getInstance().enqueueEvent(new
DisplayWidget(this,SlideShow.class,SlideLeft.class));
}

protected boolean showCheck(Display display, EventArg eventArgs) {
String photoName = PhotoList.getCurrentPhoto();
currentImage = "/"+photoName;
setTitle("Photo view - "+photoName.substring(0, photoName.length()-4));
return true;
}

}

Step 3: Adding the glue

First we need to implement the class containing the pictures index:

public class PhotoList {

private static int selectedPhotoIndex=0;
private PhotoList() {
}
public static final String[] list = {
"Church.jpg",
"Dema.jpg",
"Marco.jpg",
"Paolo.jpg",
"PizBoe.jpg",
"SellaRonda.jpg",
"Track22.jpg",
"Zoncolan.jpg"
};
public static String getNextPhoto(){
selectedPhotoIndex++;
if (selectedPhotoIndex>=list.length)
selectedPhotoIndex=0;
return getCurrentPhoto();
}
public static String getPrevPhoto(){
selectedPhotoIndex--;
if (selectedPhotoIndex<0)
selectedPhotoIndex=list.length-1;
return getCurrentPhoto();
}
public static void setCurrentPhoto(int i){
if (i>=0 && i<list.length)
selectedPhotoIndex = i;
}
public static String getCurrentPhoto(){
return list[selectedPhotoIndex];
}
public static int getCurrentPhotoIndex(){
return selectedPhotoIndex;
}
}

And finally we have to enable graphic effects in the Gear midlet (to see the transitions) and display the PhotoBrowser at midlet initialization.

setGraphicEffectsEnabled(true);
EventManager.getInstance().enqueueEvent(new DisplayWidget(this,PhotoBrowser.class));

Conclusions

The application we have just created shows how to handle different types of user inputs and gives some basic knowledge about creation of custom graphic widgets and about usage of advanced graphic effects.
It is possible to download the source code and the binary package of this application from Gear's download page.