Implementing the main MIDlet

MovieBookingMIDlet class

This class contains the code specific to the MIDlet application model. The class extends javax.microedition.midlet.MIDlet and implements its abstract methods startApp, destroyApp, and pauseApp.

The implementations are very simple. In startApp the eSWT UI thread is created and started. In destroyApp the UI thread is terminated. The implementation of pauseApp is left empty since it’s never called on S60.

private MovieBooking bookingApp;
private Thread UIThread;

protected void startApp() {
    // note that we use a different thread to
    // return from start quickly and at
    // the same time run the eSWT event loop
    if(UIThread == null) {
        bookingApp = new MovieBooking(this);
        UIThread = new Thread(bookingApp);
        UIThread.start();
    }
}

public void destroyApp(boolean unconditional) {
    // Application must exit, 
    // this stops the eSWT event loop
    bookingApp.exit();
    // Wait for the UI thread to die. 
    try {
        UIThread.join();
    } catch(InterruptedException e) {}
}

protected void pauseApp() {

}

MovieBooking class

The MovieBooking class is the starting point of the application. This class contains the eSWT event loop, creates the top-level shell and controls the other classes that build the user interface. All the state changes on the user interface are done through this class. Finally, it takes care of handling the movie database.

For more information on methods offered by the Display class and its Constructor, see the class Display. The Shell class is used for constructing windows. For more information on the methods provided by the Shell class and its constructors, see the class Shell.

In the example, the user interface thread, the top-level Shell, the event loop, and other example specific logic is in the MovieBooking class. This class is the core of the example application. It implements the Display and Shell classes, and controls the other classes that build the user interface. Note that the entire code for the MovieBooking class is given in this section. Code that implements basic features or application specific logic is not extensively commented. However, code that implements aspects of the Display and Shell classes is more extensively commented.

  1. Create the MovieBooking class file.

  2. Import the required classes and assign this class to the MovieBooking.ui package.

    package moviebooking.ui;
    
    import java.util.Vector;
    
    import javax.microedition.midlet.MIDlet;
    
    import moviebooking.moviedb.BookingTicket;
    import moviebooking.moviedb.Movie;
    import moviebooking.moviedb.MovieDB;
    import moviebooking.moviedb.MovieDBEvent;
    import moviebooking.moviedb.MovieDBListener;
    import moviebooking.moviedb.Showing;
    import org.eclipse.swt.SWT;
    import org.eclipse.swt.layout.GridLayout;
    import org.eclipse.swt.widgets.Display;
    import org.eclipse.swt.widgets.Shell;
    import org.eclipse.swt.widgets.MessageBox;
    
    public class MovieBooking implements Runnable, MovieDBListener {
    
    	private MovieDB movieDB;
    	private Shell mainShell;
    	private SplashScreen splashScreen;
    	private Display display;
    	private ReservationForm reservationForm;
    	private boolean running = false;
    	private MIDlet midlet;
    	
    	public MovieBooking(MIDlet midlet) {
    		this.midlet = midlet;
    	}
    
    	private MovieBooking() {}
  3. Implement the run method, which controls the user interface thread, the event loop, accesses the system resources, and the main Display (which is the parent of all other Displays).

    	public void run() {
    
    		running = true;
    		// Note that Display has to be created here since that
    		// determines the UI thread
    		display = new Display();

    In the code above, display = new Display() constructs a new instance of the Display class. Note that this thread must dispose of the instance before it can construct another one, although it should not be done until the MIDlet is about to enter 'destroyed' state.

    		mainShell = new Shell(display, SWT.NONE);

    In the code above, mainShell = new Shell(display, SWT.NONE) creates a top-level Shell. Note that when using eSWT, it is strongly recommended that you only have one top-level Shell. Note that SWT.NONE is provided by class SWT and is an optional, appearance related constant, which may differ between implementations. For more information on Shell constructors, see the class Shell.

    		// Create a layout for the shell
    		GridLayout layout = new GridLayout();
    		// Zero the margins that are non-zero by default
    		layout.horizontalSpacing = 0;
    		layout.verticalSpacing = 0;
    		layout.marginWidth = 0;
    		layout.marginHeight = 0;
    		mainShell.setLayout(layout);
    		mainShell.setText("MovieBooking");
    		// open first this shell to be in the background
    		mainShell.open();
    

    In the code above, mainShell.open(), which is a Shell class method, places mainShell at the top of the drawing order, marks it as visible, sets the focus, and asks to make the Shell active.

    		// Creates the splash screen
    		splashScreen = new SplashScreen(this, mainShell);
    		
    		// Create the MovieDB and request that it starts loading the data
    		movieDB = new MovieDB(getClass().getResourceAsStream(
    		"/res/movieDB.properties"));
    		movieDB.setDBListener(this);
    		// This starts loading the data on a separate thread
    		movieDB.loadData();// eSWT event loop. Run until the main shell is disposed
    
    		// eSWT event loop runs until the main shell is disposed
    		// or the running flag is set to false. 
    		// The flag can get set due to a user initiated exit or due to 
    		// a system request to terminate the application. 
    		while (running && !mainShell.isDisposed()) {
    			// Check if there are pending events
    			if (!display.readAndDispatch()) {
    				// otherwise sleep
    				display.sleep();
    			}
    		}
    
    		// Dispose the display, this will also dispose mainShell
    		display.dispose();
    		// Destroy the MIDlet
    		midlet.notifyDestroyed();
    	}

    The event loop is created and run until the main Shell is disposed. An eSWT event loop is executed in the run method and must be explicitly made visible. After the user interface has been built, the event loop is started. The event loop continuously checks that the running variable is true and that the top Shell has not been disposed off. If the event loop finds events waiting in the event queue, it uses the readAndDispatch method of the Display class to process them and sends them to their respective listeners. When there are no pending events left, the sleep method of the Display class is called to release the main processor.

  4. Implement an asynchronous event.

    	// This is called when new data is loaded by MovieDB
    	public void handleEvent(MovieDBEvent event) {
    		// check anyway if the mainShell is still alive
    		if (!mainShell.isDisposed()) {
    			// this is a non-GUI thread. we need to do  actions
    			// in the GUI thread and so asyncExec is used
    			SplashScreenRunnable runnable = new SplashScreenRunnable(event);
    			display.asyncExec(runnable);
    		}
    	}
    	// Exit method
    	public void exit() {
    		// close the DB and dispose the main shell
    		movieDB.close();
    		running = false;
    		display.wake();
    	}

    In the code above, display.wake() is a Display class method. If the user-interface thread is in sleep mode, this method can be used to wake it up and start it running again.

    	// Show the reservation form when coming back from seatingShell
    	void setSelectedSeats(int[][] selected) {
    		reservationForm.setSelectedSeats(selected);
    	}
    
    	void cancelSeatSelection() {
    		reservationForm.cancelSeatSelection();
    	}
    
    	// Show the reservation form
    	void startReserve(Movie movie) {
    		reservationForm = new ReservationForm(MovieBooking.this,
    				mainShell,
    				movie);
    	}
    
    	// Shown when the reservation is being sent to movieDB
    	void confirmReservation(String firstNameText,
    			String familyNameText, Showing showing, int[][] selectedSeats) {
    		// Ask MovieDB to request some seats
    		dataLoaded();    
    		int result = displayMessageBox(SWT.ICON_INFORMATION | SWT.YES | SWT.NO,
    				"Booking",
    		"Are you sure you want to reserve?");
    		if (result == SWT.YES) {
    			// return to the movie list      
    			movieDB.sendBookingRequest(firstNameText, familyNameText,
    					showing, selectedSeats);
    		}
    	}
    
    	// Displays a message box
    	int displayMessageBox(int type, String text, String message) {
    		if (Thread.currentThread() == display.getThread()) {
    			MessageBox box = new MessageBox(mainShell, type);
    			box.setText(text);
    			box.setMessage(message);
    			return box.open();
    		} else {
    			return -1;
    		}
    	}

    In the code above, the eSWT API class MessageBox is used to provide the user some information on the ongoing reservation process.

    	// Shows the seating dialog
    	void showSeatingDialog(Showing showing, int[][] selectedSeats,
    			int ticketsCount) {
    		new SeatingScreen(this, mainShell, showing,
    				selectedSeats, ticketsCount);
    	}
    
    	// Advance the progress bar in SplashShell
    	private void advanceSplashShellProgress(String message) {
    		splashScreen.setLabel(message);
    		splashScreen.advance();
    	}
    
    	// Called if an error occured in movieDB
    	private void movieDBDataError() {
    		displayMessageBox(SWT.ICON_ERROR,
    				"Information",
    		"Error loading the database");
    		// Exit the swt loop
    		mainShell.dispose();
    	}

    In the code above, the eSWT API class MessageBox is used to warn the user about an error that has happened with the MovieDB.

    	// Set the total amount of movies to display
    	private void setMovieCount(int count) {
    		splashScreen.setMoviesCount(count);
    	}
    
    	// Show a dialog box when a reservation has been confirmed
    	private void displayReservationConfirmed(BookingTicket booking) {
    		displayMessageBox(SWT.ICON_INFORMATION | SWT.OK,
    				"Booking",
    				"Reservation confirmed number " + booking.getCode()
    				+ " total: " + booking.getPrice());
    	}
    
    	// Called when all the data in MovieDB is
    	// available. We close splashShell and
    	// start Movie Display
    	private void dataLoaded() {
    		if (splashScreen != null) {
    			splashScreen.destroy();
    			splashScreen = null;
    		}
    		Vector movies = movieDB.getMovies();
    		if (movies.size() == 0) {
    			// No movie found
    			displayMessageBox(SWT.ICON_INFORMATION | SWT.OK,
    					"Information",
    			"No movies available");
    			// Exit the swt loop
    			mainShell.dispose();
    		} else {
    			// Start the new MovieDisplay screen
    			new MoviesListScreen(MovieBooking.this, mainShell, movies);
    		}
    	}
    
    	// Inner class used to call actions on the GUI
    	// being initiated in MovieDB
    	private class SplashScreenRunnable implements Runnable {
    
    		private MovieDBEvent event;
    
    		SplashScreenRunnable(MovieDBEvent event) {
    			this.event = event;
    		}
    
    		public void run() {
    			// Note that this is called from the GUI thread
    			switch (event.getEvent()) {
    			case MovieDBEvent.CONNECTING:
    				// When connecting update the progress label
    				// in the splash shell
    				advanceSplashShellProgress("Loading from " + (String) event.getData());
    				break;
    			case MovieDBEvent.DATA_ERROR:
    				movieDBDataError();
    				break;
    			case MovieDBEvent.MOVIES_COUNT:
    				setMovieCount(((Integer) event.getData()).intValue() + 1);
    				break;
    			case MovieDBEvent.MOVIE_LOADED:
    				// Called when data for a new movie is available
    				advanceSplashShellProgress("Movie "
    						+ (((Integer) event.getData()).intValue() + 1));
    				break;
    			case MovieDBEvent.DATA_LOADED:
    				dataLoaded();
    				break;
    			case MovieDBEvent.MOVIE_RESERVED:
    				displayReservationConfirmed((BookingTicket) event.getData());
    				break;
    			}
    		}
    	}
    
    }