Implementing the Panorama MIDlet

Panorama.java

Panorama is the MIDlet main class.

  1. Import the required classes. This example uses the common MIDlet and some LCDUI classes, and a number of Mobile Sensor imports. The DeviceControl class from the Nokia UI API is used to keep the backlight on continuously.

    import java.io.IOException;
    
    import javax.microedition.io.Connector;
    import javax.microedition.lcdui.Display;
    import javax.microedition.lcdui.Image;
    import javax.microedition.midlet.MIDlet;
    import javax.microedition.midlet.MIDletStateChangeException;
    import javax.microedition.sensor.ChannelInfo;
    import javax.microedition.sensor.Data;
    import javax.microedition.sensor.DataListener;
    import javax.microedition.sensor.SensorConnection;
    import javax.microedition.sensor.SensorInfo;
    import javax.microedition.sensor.SensorManager;
    
    import com.nokia.mid.ui.DeviceControl;
  2. Create the main class and initial parameters to be used in the example. iCanvas is an instance of PanoramaCanvas, which is created later.

    public class Panorama extends MIDlet implements DataListener, PanoramaCanvas.ExitIf {
    
        static final int BUFFER_SIZE = 3;
        private static PanoramaCanvas iCanvas;
        private static SensorConnection iConnection;
        private static final String PHOTO_NAME = "/for_more_see_roundus_dot_com.jpg";
        public Panorama()
        {
        }
  3. Create the data listening method. Note the reference to the setPosition method, which is created in PanoramaCanvas.

        public void dataReceived( SensorConnection con, Data[] aData, boolean aMissed)
        {
            iCanvas.setPosition(aData);
        }
  4. Create a method for exiting the application. This is received from the PanoramaCanvas.ExitIf method that was extended in step 1.

        public void exit()
        {
            try
            {
                destroyApp( true );
            }
            catch (MIDletStateChangeException e)
            {
                e.printStackTrace();
            }
            notifyDestroyed();
        }
  5. Create the mandatory MIDlet methods. startApp initializes PanoramaCanvas and sets it as the current Display, opens the sensor connection, and avoids backlight fadeout is by using DeviceControl.setLights.

        protected void destroyApp(boolean arg0) throws MIDletStateChangeException
        {
            try
            {
                if (iConnection!=null)
                    iConnection.close();
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    
        protected void pauseApp()
        {
        }
    
        protected void startApp() throws MIDletStateChangeException
        {
            DeviceControl.setLights( 0, 100 );
            Display disp = Display.getDisplay( this );
            try
            {
                iCanvas = new PanoramaCanvas( Image.createImage(PHOTO_NAME), this );
                disp.setCurrent( iCanvas );
                iConnection = openAccelerationSensor();
                if (iConnection != null)
                    iConnection.setDataListener( this, BUFFER_SIZE );
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
  6. Create a method for opening the sensor connection. As devices based on the Symbian platform have two data return formats for acceleration sensors, the following method selects the correct one by its data format (integer in this case) with the ChannelInfo.TYPE_INT parameter.

        private SensorConnection openAccelerationSensor(){
    
            SensorInfo[] infos = SensorManager.findSensors("acceleration", null);
            if (infos.length==0) return null;
    
            // INT data type is preferred
            int i=0;
            for (i=0; i<infos.length && infos[i].getChannelInfos()[0].getDataType()!=ChannelInfo.TYPE_INT; i++);
    
            try{
                return i==infos.length ? (SensorConnection)Connector.open(infos[0].getUrl()):
                    (SensorConnection)Connector.open(infos[i].getUrl());
            }catch (Exception e) {
                return null;
            }
        }
    }

PanoramaCanvas.java

PanoramaCanvas determines whether the image is in portrait or landscape orientation and calculates the image scrolling from the orientation changes of the device. It is also possible to traverse the image with arrow key presses.

  1. Import the needed classes. PanoramaCanvas mostly uses the LCDUI Canvas and related classes.

    import java.io.IOException;
    
    import javax.microedition.lcdui.Canvas;
    import javax.microedition.lcdui.Graphics;
    import javax.microedition.lcdui.Image;
    import javax.microedition.sensor.Data;
    
    import com.nokia.mid.ui.DeviceControl;
  2. Set the required variables.

    public class PanoramaCanvas extends Canvas
    {
    
        private static final int STEP = 5;
        private static ExitIf iExit;
        private static Image iImage;
        private static int iImageHeight;
        private static int iImageWidth;
        private static int x = 0;
        private static int y = 0;
        private static int ii = 0;
  3. Continue setting values for the parameters. Add a repaint method for drawing the changes on the screen.

        public PanoramaCanvas(  Image aImage, ExitIf aExit) throws IOException
        {
            super();
            setFullScreenMode( true );
    
            iExit = aExit;
            iImage = aImage;
            iImageHeight = iImage.getHeight();
            iImageWidth = iImage.getWidth();
            y=-(iImageHeight-getHeight())/ 2;
    
            repaint();
        }
  4. Add a method to use the received accelerometer values and react to them by moving the image on the screen. In this example, full screen mode is also forced by default.

        void setPosition(Data[] aData){
    
            if (ii++%100==0){
                DeviceControl.setLights(0, 100);   //to keep backlight on
                setFullScreenMode( true );
            }
    
            x = getX(x+=getX(aData));
            y = getY(y+=getY(aData));
    
            repaint();
        }
  5. Add functions to deal with directional key presses. In this case, all key presses scroll the image as much as is set in the STEP variable. keyRepeated handles a similar function for held down keys.

        /**
         * deal with any key presses
         */
        protected void keyPressed(int keyCode) {
            System.out.println("keyCode =" +keyCode);
            switch(keyCode){
            case -1:    //up
            case Canvas.UP:
                y = getY(y+=STEP);
                break;
            case -2: //down
            case Canvas.DOWN:
                y = getY(y+=-STEP);
                break;
            case -3:    //left
            case Canvas.LEFT:
                x = getX(x+=STEP);
                break;
            case -4: //right
            case Canvas.RIGHT:
                x = getX(x+=-STEP);
                break;
            default:
            }
            repaint();
    
        }
    
        protected void keyRepeated(int keyCode){
            keyPressed(keyCode);
        }
  6. Create the paint method to move the image on the Canvas.

        protected void paint( Graphics g ){
            g.drawImage( iImage, x , y, Graphics.TOP | Graphics.LEFT );
            if( x + iImageWidth - getWidth() < 0 )
                g.drawImage( iImage, x + iImageWidth, y, Graphics.TOP | Graphics.LEFT );
    
        }
  7. Create a few additional functions. Here, pointerPressed is set to activate the exit so that the user can exit the MIDlet by pressing on the Selection Key or tapping on the screen, and sizeChanged is called to center the image on the screen in cases where a size change occurs.

        protected void pointerPressed(int x, int y) {iExit.exit();}
    
        protected void sizeChanged(int w, int h) {
            y=-(iImageHeight-getHeight())/ 2;
            repaint();
        }
  8. Detect the image size and create the image scrolling functionality accordingly.

        private int getX(Data[] aData){
            int x_axis = 0;
            boolean isPortrait = getHeight()>getWidth();
            int index= isPortrait? 0: 1;
    
            try{
                for (int i=0; i<Panorama.BUFFER_SIZE; i++){
                    x_axis += aData[index].getIntValues()[0];
                }
                x_axis = (int)(x_axis/Panorama.BUFFER_SIZE);
            }catch (IllegalStateException e) {
                for (int i=0; i<Panorama.BUFFER_SIZE; i++){
                    x_axis += (int)aData[index].getDoubleValues()[0];
                }
                x_axis = (int)(x_axis/Panorama.BUFFER_SIZE);
            }
    
            return isPortrait?-x_axis%iImageWidth:x_axis%iImageWidth;
        }
    
        private int getY(Data[] aData){
            int y_axis = 0;
            boolean isPortrait = getHeight()>getWidth();
            int index= isPortrait? 1: 0;
    
            try{
                for (int i=0; i<Panorama.BUFFER_SIZE; i++){
                    y_axis += aData[index].getIntValues()[0];
                }
                y_axis = (int)(y_axis/Panorama.BUFFER_SIZE);
            }catch (IllegalStateException e) {
                for (int i=0; i<Panorama.BUFFER_SIZE; i++){
                    y_axis += (int)aData[index].getDoubleValues()[0];
                }
                y_axis = (int)(y_axis/Panorama.BUFFER_SIZE);
            }
    
            return y_axis%iImageHeight;
        }
    
        private int getX(int x){
            x = x>0?-iImageWidth+x:x;
            return x % iImageWidth;
        }
    
        private int getY(int y){
            y = y>0?0:y;    // upper limit
            return Math.abs(y)>iImageHeight-getHeight()?getHeight()-iImageHeight:y; //bottom limit
        }
  9. Set the ExitIf interface to call exit. ExitIf is used in the Panorama.destroyApp method.

        static interface ExitIf
        {
            public void exit();
        };
    
    }