eSWT - MovieBooking

This section provides the source code for the MovieBooking example.

MovieBookingMIDlet

package moviebooking;

import javax.microedition.midlet.*;
import moviebooking.ui.MovieBooking;

/**
 * Entry point of the application. This class only starts 
 * MovieBooking in a separate Thread and returns. 
 */
public class MovieBookingMIDlet extends MIDlet {

	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

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;

/**
 * The class MovieBooking is the core of the application It takes care
 * of managing the user interface, drive the eSWT event loop and talk
 * to the Movie Database
 */
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() {}

	// In the run method we execute the eSWT event loop
	public void run() {

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

		// Create the main shell
		mainShell = new Shell(display, SWT.NONE);
		mainShell.setText("MovieBooking");
		
		// 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);	
		
		// open first this shell to be in the background
		mainShell.open();

		// 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 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();
	}

	// 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();
	}

	// 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;
		}
	}

	// 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();
	}

	// 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;
			}
		}
	}

}

MoviesListScreen

package moviebooking.ui;

import java.io.InputStream;
import java.io.IOException;

import moviebooking.moviedb.Movie;

import org.eclipse.ercp.swt.mobile.Command;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.Shell;

/**
 * This class takes the list of movies and displays it to the user
 * The list of movies is shown at the right and a description of each
 * at the left.
 */
class MoviesListScreen implements SelectionListener {

	private Command reserveMovieCommand, exitCommand;
	private MovieBooking main;
	private List moviesList;
	private java.util.Vector movies;
	private Composite mainComposite;
	private Image image;
	private Label imageLabel;
	private Label descriptionText;

	MoviesListScreen(MovieBooking main, Shell mainShell,
			java.util.Vector movies) {
		this.main = main;
		this.movies = movies;

		mainShell.setText("Movies");

		mainComposite = new Composite(mainShell, SWT.NO_BACKGROUND);
		
		// Size and location of this Composite are controlled by 
		// the GridLayout of the mainShell. 
		mainComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		mainShell.layout();
		
		// This contains the list of movies
		moviesList = new List(mainComposite, SWT.BORDER);
		
		// Widgets to describe the movie
		imageLabel = new Label(mainComposite, SWT.TOP | SWT.CENTER);
		descriptionText = new Label(mainComposite, SWT.WRAP | SWT.CENTER);

		// Populate moviesList
		for(int i = 0; i < movies.size(); ++i) {
			Movie m = (Movie)movies.elementAt(i);
			moviesList.add(m.getName());
		}
		if (movies.size() > 0) {
			moviesList.select(0);
			movieSelected(0);
		}
		moviesList.addSelectionListener(this);

		// Distribute the widgets using a FormLayout
		FormLayout layout = new FormLayout();
		mainComposite.setLayout(layout);

		// Set movies list location
		FormData moviesListData = new FormData();
		moviesListData.top = new FormAttachment(0);
		moviesListData.left = new FormAttachment(0);
		moviesListData.right = new FormAttachment(50);
		moviesListData.bottom = new FormAttachment(100);
		moviesList.setLayoutData(moviesListData);

		// Set image location
		FormData imageLabelData = new FormData();
		imageLabelData.top = new FormAttachment(moviesList, 0, SWT.TOP);
		imageLabelData.left = new FormAttachment(moviesList);
		imageLabelData.right = new FormAttachment(100);
		imageLabel.setLayoutData(imageLabelData);

		// Set image location
		FormData descriptionTextData = new FormData();
		descriptionTextData.top = new FormAttachment(imageLabel);
		descriptionTextData.left = new FormAttachment(imageLabel, 0, SWT.LEFT);
		descriptionTextData.right = new FormAttachment(100);
		descriptionTextData.bottom = new FormAttachment(moviesList, 0, SWT.BOTTOM);
		descriptionText.setLayoutData(descriptionTextData);

		// Add commands
		exitCommand = new Command(mainComposite, Command.EXIT, 0);
		exitCommand.setText("Exit");
		exitCommand.addSelectionListener(this);

		reserveMovieCommand = new Command(mainComposite, Command.GENERAL, 1);
		reserveMovieCommand.setText("Reserve");
		reserveMovieCommand.addSelectionListener(this);
		reserveMovieCommand.setDefaultCommand();
	}

	// Called when a command is invoked or a movie is selected
	public void widgetSelected(SelectionEvent e) {
		if (e.widget == exitCommand) {
			destroy();
			main.exit();
		} else if (e.widget == reserveMovieCommand) {
			if (moviesList.getSelectionIndex() >= 0) {
                // Order of operations is important.
                // We need to destroy before reserve that will create a new UI. 
                // But need to call moviesList.getSelectionIndex() before destroy. 
				Movie selectedMovie = (Movie)movies.elementAt(moviesList.getSelectionIndex());
				destroy();
				main.startReserve(selectedMovie);
			}
		} else if (e.widget == moviesList) {
			// This is called when the user selects a movie
			movieSelected(moviesList.getSelectionIndex());
		}
	}

	public void widgetDefaultSelected(SelectionEvent e) {
		// Do nothing
	}

	// Called when a movie is selected.
	// Take the picture and name and put in the
	// right side window. 
	private void movieSelected(int index) {
		if (index >= 0) {
			Movie m = (Movie) movies.elementAt(index);
			if (m != null) {
				if (m.getImageLocation()!= null) {
					try {
						InputStream stream = getClass().getResourceAsStream(
								m.getImageLocation());             
						if (stream != null) {
							Image newImage = new Image(mainComposite.getDisplay(), stream);
							imageLabel.setImage(newImage);
							if(image != null) {
								image.dispose();
							}
							image = newImage;
							stream.close();
						}
					} catch (IOException e) {
						// Do nothing, a picture is missing
					}
					descriptionText.setText(m.getDescription());
				}
			}
		}
	}

	private void destroy() {
		// Dispose the widgets by disposing the parent
		mainComposite.dispose();    
		// Dispose the current image
		if (image != null && !image.isDisposed()) {
			image.dispose();
		}
	}

}

ReservationForm

package moviebooking.ui;

import java.util.Vector;
import java.util.Calendar;
import java.util.Date;
import java.util.Hashtable;

import moviebooking.moviedb.Movie;
import moviebooking.moviedb.Showing;

import org.eclipse.ercp.swt.mobile.Command;
import org.eclipse.ercp.swt.mobile.ConstrainedText;
import org.eclipse.ercp.swt.mobile.DateEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Combo;

/**
 * This class contains a common Form to enter the data needed to make
 * a reservation. It contains fields such as first name,
 * family name date, etc.
 * 
 * The seating location is obtained in a separate form
 */
public class ReservationForm implements SelectionListener, ModifyListener {

	private MovieBooking main;
	private Command exitCommand, seatCommand, confirmCommand;
	private DateEditor movieDate;
	private Combo movieTime;
	private Composite mainComposite;
	private Hashtable validDates = new Hashtable();  
	private Text firstNameText, familyNameText;
	private ConstrainedText ticketsText;
	private int[][] selectedSeats;

	ReservationForm(MovieBooking main, Shell mainShell, Movie movie) {
		this.main = main;
		mainShell.setText("Reservation");

		mainComposite = new Composite(mainShell, SWT.NONE);

		// Size and location of this Composite are controlled by 
		// the GridLayout of the mainShell
		mainComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
	
		// Create all the widgets in the Composite
		Label firstNameLabel = new Label(mainComposite, SWT.NONE);
		firstNameLabel.setText("First name");
		// Contains the first name
		firstNameText = new Text(mainComposite, SWT.SINGLE | SWT.BORDER);
		firstNameText.setTextLimit(20);

		Label familyNameLabel = new Label(mainComposite, SWT.NONE);
		familyNameLabel.setText("Family name");
		// Contains the family name
		familyNameText = new Text(mainComposite, SWT.SINGLE | SWT.BORDER);
		familyNameText.setTextLimit(20);

		Label dateLabel = new Label(mainComposite, SWT.NONE);
		dateLabel.setText("Date");
		// Date for the movie, by default today
		movieDate = new DateEditor(
				mainComposite,
				SWT.NONE,
				DateEditor.DATE | SWT.BORDER);
		movieDate.setDate(new Date());
		movieDate.addModifyListener(this);

		// A drop-down list of times
		Label timeLabel = new Label(mainComposite, SWT.NONE);
		timeLabel.setText("Time");
		movieTime = new Combo(mainComposite, SWT.DROP_DOWN | SWT.READ_ONLY | SWT.BORDER);

		Vector showings = movie.getShowings();
		Vector times = new Vector();
		// Store in the time combo the time valid
		// for all showings, validDates contains 
		// a map between showingDate and showing
		for(int i = 0; i < showings.size(); ++i) {
			Showing show = (Showing)showings.elementAt(i);
			Calendar cal = Calendar.getInstance();
			cal.setTime(show.getDate());
			String showingTime = cal.get(Calendar.HOUR_OF_DAY) + ":00";
			if (!times.contains(showingTime)) {
				times.addElement(showingTime);
			}
			validDates.put(show.getDate(), show);
		}
		for(int i = 0; i < times.size(); ++i) {
			movieTime.add((String) times.elementAt(i));
		}
		movieTime.select(0);
		movieTime.addSelectionListener(this);

		Label ticketsLabel = new Label(mainComposite, SWT.NONE);
		ticketsLabel.setText("Tickets");

		// Amount of tickets, note that ConstrainedText only takes numbers
		ticketsText = new ConstrainedText(mainComposite, SWT.SINGLE | SWT.BORDER, ConstrainedText.NUMERIC);
		ticketsText.addModifyListener(this);
		ticketsText.setText("2");

		// Use a grid layout with two columns
		GridLayout layout = new GridLayout();
		layout.makeColumnsEqualWidth = false;
		layout.numColumns = 2;
		mainComposite.setLayout(layout);

		// Distribute the forms using the layout
		firstNameText.setLayoutData(
				new GridData(SWT.FILL, SWT.CENTER, true, false));
		familyNameText.setLayoutData(
				new GridData(SWT.FILL, SWT.CENTER, true, false));
		ticketsText.setLayoutData(
				new GridData(SWT.FILL, SWT.CENTER, true, false));
		movieDate.setLayoutData(
				new GridData(SWT.FILL, SWT.CENTER, true, false));
		movieTime.setLayoutData(
				new GridData(SWT.FILL, SWT.CENTER, true, false));

		setCommands();
		
		mainShell.layout();
	}

	private void setCommands() {
		// add commands
		exitCommand = new Command(mainComposite, Command.EXIT, 0);
		exitCommand.setText("Exit");
		exitCommand.addSelectionListener(this);

		seatCommand = new Command(mainComposite, Command.GENERAL, 1);
		seatCommand.setText("Seating");
		seatCommand.addSelectionListener(this);
	}

	// Called when some command is selected
	public void widgetSelected(SelectionEvent e) {
		if (e.widget == exitCommand) {
			// Exit the main application
			destroy();
			main.exit();
		} else if (e.widget == movieTime) {
			selectedSeats = null;
			if (confirmCommand != null) {
				confirmCommand.dispose();
				confirmCommand = null;      
			}
		} else if (e.widget == seatCommand) {
			// Iterate over the valid dates
			Date showingDate = calculateTime();
			
			if (validDates.containsKey(showingDate) || (ticketsText.getText().length() == 0)) {        
				int ticketsCount = 0;
				try {
					ticketsCount = Integer.parseInt(ticketsText.getText());
				} catch (NumberFormatException ex) {
					// Ignore, just use 0
				}
				if (ticketsCount == 0) {
					return;
				}
				Showing show = (Showing) validDates.get(showingDate);
				exitCommand.dispose();
				exitCommand = null;
				seatCommand.dispose();
				seatCommand = null;
				if (confirmCommand != null) {
					confirmCommand.dispose();
					confirmCommand = null;
				}
				main.showSeatingDialog(show, selectedSeats, ticketsCount);
			} else {
				main.displayMessageBox(SWT.ICON_ERROR | SWT.OK,
						"Error",
				"Wrong date selected");
			}
		} else if (e.widget == confirmCommand) {
			Date showingDate = calculateTime();
			// Validate the fields
			if (selectedSeats == null || firstNameText.getText().equals("")
					|| familyNameText.getText().equals("")) {        
				main.displayMessageBox(SWT.ICON_ERROR | SWT.OK,
						"Error",
				"Missing required fields");
				return;
			} else {
				// Request to main to make the reservation
				String firstName = firstNameText.getText();
				String familyName = familyNameText.getText();
				destroy();
				main.confirmReservation(firstName,
						familyName, (Showing) validDates
						.get(showingDate), selectedSeats);
			}
		}
	}

	public void widgetDefaultSelected(SelectionEvent e) {}

	// Called when the user has selected the seats
	void setSelectedSeats(int[][] selected) {
		this.selectedSeats = selected;
		setCommands();
		if (confirmCommand == null && selectedSeats != null) {
			// Add a new command      
			confirmCommand = new Command(
					mainComposite,
					Command.GENERAL,
					1);
			confirmCommand.setText("Confirm");
			confirmCommand.addSelectionListener(this);
			confirmCommand.setDefaultCommand();
		}
	}

	void cancelSeatSelection() {
		selectedSeats = null;
		if (confirmCommand != null) {
			confirmCommand.dispose();
			confirmCommand = null;      
		}
		setCommands();
	}

	// Close the main composite and dispose it
	private void destroy() {
		mainComposite.dispose();    
	}

	// Create a time based on movieDate and timeCombo
	private Date calculateTime() {
		if (movieTime.getSelectionIndex() == -1) {
			return null;
		} else {
			Calendar cal = Calendar.getInstance();
			// Take the date from movieDate
			Date showingDate = movieDate.getDate();  
			cal.setTime(showingDate);

			// Get the time from movieTime, so we remove the last ":00"
			// Notice that we expect only exact minutes
			String time = movieTime.getItem(movieTime.getSelectionIndex());
			int hourOfDay = Integer.parseInt(time.substring(0, time.indexOf(':')));
			int minutes = Integer.parseInt(time.substring(time.indexOf(':') + 1, time.length() - 1));

			cal.set(Calendar.HOUR_OF_DAY, hourOfDay);
			cal.set(Calendar.MINUTE, minutes);
			cal.set(Calendar.SECOND, 0);
			cal.set(Calendar.MILLISECOND, 0);
			return cal.getTime();
		}
	}

	public void modifyText(ModifyEvent e) {
		selectedSeats = null;
		if (confirmCommand != null) {
			confirmCommand.dispose();
			confirmCommand = null;      
		}
	}

}

SeatingCanvas

package moviebooking.ui;

import moviebooking.moviedb.Showing;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Shell;

/**
 * Contains the seat distribution and facilities to select it
 */
class SeatingCanvas extends Canvas
implements PaintListener, ControlListener, KeyListener {

	private int w, h;
	private Showing showing;
	private int[][] selected;
	private Image doubleBuffer;
	private GC gc;
	private boolean[][] seats;
	private int seatCount;
	private int selectedRow, firstSelectedSeat;
	private int rows;
	private int seatsPerRow;
	private Shell shell;

	SeatingCanvas(Shell shell, Showing showing,
			int[][] selectedSeating, int seatCount) {
		super(shell, SWT.NO_BACKGROUND);
		this.showing = showing;
		this.shell = shell;
		this.seatCount = seatCount;
		this.selected = selectedSeating;
		seats = showing.getSeating();
		rows = seats.length;
		seatsPerRow = seats[0].length;

		init();
	}
	
	void init() {
		// Obtain the size of the parent
		Rectangle clientArea = getClientArea();
		w = clientArea.width;
		h = clientArea.height;
		// Select the first free seats
		selected = new int[seatCount][2];
		for (int i = 0; i < rows; i++) {
			if (searchSeatsInRow(i, 0)) {
				selectedRow = i;
				break;
			}
		}
		// This is necessary to repaint the screen
		addPaintListener(this);
		// Request to listen for keys
		addKeyListener(this);
		// Listen if the size of the parent changes
		shell.addControlListener(this);
	}

        	public void paintControl(PaintEvent e) {
		//	Dump the offscreen image onto the canvas
		drawSeating();
		e.gc.drawImage(doubleBuffer, 0, 0);
	}
	
        	// Drawing of the offscreen image
	private void drawSeating() {	
		int width = (95 * w) / 100;
		int height = h / 20;

		// The movie theather's screen
		Rectangle screen = new Rectangle(
				(w - width) / 2,
				height,
				width,
				height);
		
		Color bgColor = getDisplay().getSystemColor(SWT.COLOR_WHITE);
		Color occupiedSeatColor = getDisplay().getSystemColor(SWT.COLOR_RED);
		Color availableSeatColor = getDisplay().getSystemColor(SWT.COLOR_BLUE);
		Color frameColor = getDisplay().getSystemColor(SWT.COLOR_BLACK);
		Color selectedSeatColor = getDisplay().getSystemColor(SWT.COLOR_GREEN);
		Color screenColor = getDisplay().getSystemColor(SWT.COLOR_GRAY);

		if(doubleBuffer == null ) {
			doubleBuffer = new Image(getDisplay(), getBounds());
		}
		if(gc == null) {
			gc = new GC(doubleBuffer);
		}
		
		// Clear the image
		gc.setBackground(bgColor);
		gc.fillRectangle(0, 0, w, h);
				
		gc.setForeground(frameColor);
		gc.drawRectangle(0, 0, w - 1, h - 1);

		gc.setForeground(screenColor);
		gc.setBackground(screenColor);
		gc.fillRectangle(screen);

		boolean[][] seats = showing.getSeating();
		int rows = seats.length;
		int seatsPerRow = seats[0].length;
		int radius = Math.min((screen.width / seatsPerRow),
				((h - (screen.height + screen.y)) / rows)) - 2;
		// Space per seat/row
		int spacex = screen.width / seatsPerRow;
		int spacey = ((h - (screen.height + screen.y + radius / 2)) / rows);
		// Empty space to leave for horizontal center alignment
		int xOffset = (screen.width - spacex * seatsPerRow) / 2;
		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < seatsPerRow; j++) {
				if (seats[i][j]) {
					gc.setBackground(occupiedSeatColor);
				} else {
					gc.setBackground(availableSeatColor);
				}
				gc.fillOval(screen.x + xOffset + j * spacex, (screen.y
						+ screen.height + radius / 2)
						+ i * spacey, radius, radius);
			}
		}
		for (int i = 0; i < selected.length; i++) {
			gc.setBackground(selectedSeatColor);
			gc.fillOval(screen.x + xOffset + selected[i][1] * spacex, (screen.y
					+ screen.height + radius / 2)
					+ selected[i][0] * spacey, radius, radius);
		}
	}
        
	public void keyPressed(KeyEvent e) {
		// Check if the arrow keys are pressed
            if (e.keyCode == SWT.ARROW_UP) {
			if (selectedRow > 0) {
				searchSeatsInRow(selectedRow - 1, firstSelectedSeat);
				redraw();
			}
		} else if (e.keyCode == SWT.ARROW_DOWN) {
			if (selectedRow < (rows - 1)) {
				searchSeatsInRow(selectedRow + 1, firstSelectedSeat);
				redraw();
			}
		} else if (e.keyCode == SWT.ARROW_LEFT) {
			if (firstSelectedSeat > 0) {
				searchSeatsInRow(selectedRow, firstSelectedSeat - 1);
				redraw();
			}
		} else if (e.keyCode == SWT.ARROW_RIGHT) {
			if (firstSelectedSeat < (seatsPerRow - seatCount)) {
				searchSeatsInRow(selectedRow, firstSelectedSeat + 1);
				redraw();
			}
		}
	}

	public void keyReleased(KeyEvent e) {
            // Nothing to do, we are only interested in key releases		
	}


        
        public void controlMoved(ControlEvent e) {
		// We are not interested in this event
	}

	public void controlResized(ControlEvent e) {
		// Update the width and height
		Rectangle clientArea = ((Composite) e.widget).getClientArea();
		w = clientArea.width;
		h = clientArea.height;
		// Recreate the double buffer with the new size
		if(doubleBuffer != null) {
			doubleBuffer.dispose();
		}
		doubleBuffer = null;
		if(gc != null){
			gc.dispose();
		}
		gc = null;
		
		redraw();
	}

	// Returns the currently selected seats
	int[][] getSelectedSeats() {
		return selected;
	}
	
	public void destroy() {
		if (doubleBuffer != null) {
			doubleBuffer.dispose();
		}
		if (gc != null) {
			gc.dispose();
		}
	}
	
	// Search if seatCount seats are available in row i
	private boolean searchSeatsInRow(int i, int firstSelectedSeat) {
		int k = 0;
		for (int j = firstSelectedSeat; j < seatsPerRow; j++) {
			if (!seats[i][j]) {
				k++;
			}
		}
		if (k >= seatCount) {
			k = 0;
			for (int j = firstSelectedSeat; j < seatsPerRow
			&& k < seatCount; j++) {
				if (!seats[i][j]) {
					selected[k][0] = i;
					selected[k++][1] = j;
				}
			}
			selectedRow = i;
			this.firstSelectedSeat = selected[0][1];
			return true;
		} else {
			return false;
		}
	}
	
}

SeatingScreen

package moviebooking.ui;

import moviebooking.moviedb.Showing;

import org.eclipse.ercp.swt.mobile.Command;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;

/**
 * Simple shell that contain the SeatingCanvas
 */
class SeatingScreen implements SelectionListener, ControlListener {

	private Command exitCommand, okCommand, backCommand;
	private MovieBooking main;
	private Shell seatingShell;
	private Shell mainShell;
	private SeatingCanvas seatingCanvas;

	SeatingScreen(MovieBooking main, Shell mainShell, Showing showing,
			int[][] selectedSeats, int ticketsCount) {
		this.main = main;

		this.mainShell = mainShell;
		
		seatingShell = new Shell(mainShell, SWT.BORDER | SWT.TITLE);
		seatingShell.setText("Seats");
		seatingShell.open();

		// Add a ControlListener to watch the size changes of the top-level 
		// Shell and update the seatingShell's size
		mainShell.addControlListener(this);

		// Seating canvas displays the seating arrangement
		seatingCanvas = new SeatingCanvas(
				seatingShell,
				showing,
				selectedSeats,
				ticketsCount);

		// Size and location of the seatingCanvas are controlled by
		// The FillLayout of the seatingShell. 
		seatingShell.setLayout(new FillLayout());
		seatingShell.layout();
		
		// add some commands
		exitCommand = new Command(seatingShell, Command.EXIT, 0);
		exitCommand.setText("Exit");
		exitCommand.addSelectionListener(this);

		okCommand = new Command(seatingShell, Command.OK, 1);
		okCommand.setText("Select");
		okCommand.addSelectionListener(this);

		backCommand = new Command(seatingShell, Command.BACK, 0);
		backCommand.setText("Back");
		backCommand.addSelectionListener(this);
	}

	private void destroy() {		
		mainShell.removeControlListener(this);
		seatingCanvas.destroy();
		seatingShell.dispose();
	}

	public void widgetSelected(SelectionEvent e) {
		if (e.widget == exitCommand) {
			destroy();
			main.exit();
		} else if (e.widget == okCommand) {
			destroy();
			main.setSelectedSeats(seatingCanvas.getSelectedSeats());
		} else if (e.widget == backCommand) {
			destroy();
			main.cancelSeatSelection();
		}

	}

	public void widgetDefaultSelected(SelectionEvent e) {
		// Nothing to do
	}

	public void controlMoved(ControlEvent e) {
		// Can be left at (0, 0)
	}
	
	public void controlResized(ControlEvent e) {
		//seatingShell.setBounds(((Control)e.widget).getBounds());
		Rectangle rect = ((Control)e.widget).getBounds();
		rect.x += rect.width/4;
		rect.y += rect.height/4;
		rect.height /= 2;
		rect.width /= 2;
		seatingShell.setBounds(rect);
	}
}

SplashScreen

package moviebooking.ui;

import java.io.InputStream;
import java.io.IOException;
import org.eclipse.ercp.swt.mobile.Command;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;

/**
 * Simple splash screen it display the progress while loading the
 * MovieDB
 */
class SplashScreen implements SelectionListener {

	private Composite splashComposite;
	private Label progressLabel;
	private ProgressBar progressBar;
	private Command exitCommand;
	private MovieBooking main;
	private Image appImage;

	SplashScreen(MovieBooking main, Shell mainShell) {
		this.main = main;

		// Parent Composite of the splash screen widgets
		splashComposite = new Composite(mainShell, SWT.NONE);
		
		// splashComposite's size and location are controlled
		// by the GridLayout of the mainShell
		splashComposite.setLayoutData(
				new GridData(SWT.FILL, SWT.FILL, true, true));

		// Load the application's image
		InputStream imageStream = this.getClass().getResourceAsStream("/res/splash.png");
		if (imageStream != null) {
			appImage = new Image(mainShell.getDisplay(), imageStream);
			try {
				imageStream.close();
			} catch (IOException e) {
				// ignore, in this case it is not relevant
			}
		}
                
		// Create the widgets
		Label titleLabel = new Label(splashComposite, SWT.CENTER);
		titleLabel.setText("MovieBooking");

		Label imageLabel = new Label(splashComposite, SWT.CENTER);
		if (appImage != null) {
			imageLabel.setImage(appImage);
		}

		progressLabel = new Label(splashComposite, SWT.CENTER);
		progressLabel.setText("Loading...");

		progressBar = new ProgressBar(splashComposite, SWT.CENTER);
		progressBar.setMaximum(10);
		progressBar.setMinimum(0);

		// Distribute the widgets using a FormLayout
		FormLayout layout = new FormLayout();
		splashComposite.setLayout(layout);

		// Set the image at the center
		FormData imageLabelData = new FormData();
		Point imageSize = imageLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT);
		imageLabelData.top = new FormAttachment(50, -imageSize.y/2);
		imageLabelData.left= new FormAttachment(50, -imageSize.x/2);
		imageLabel.setLayoutData(imageLabelData);
		
		// Set title label above the image
		FormData titleLabelData = new FormData();
		titleLabelData.left = new FormAttachment(imageLabel, 0, SWT.CENTER);
		titleLabelData.bottom = new FormAttachment(imageLabel, 0, SWT.TOP);
		titleLabel.setLayoutData(titleLabelData);
			
		// Set the progress bar at the center, below the title label
		FormData progressBarData = new FormData();
		progressBarData.left = new FormAttachment(imageLabel, 0, SWT.CENTER);
		progressBarData.top = new FormAttachment(imageLabel);
		progressBar.setLayoutData(progressBarData);

		// Set the progress label below the progress bar
		FormData progressLabelData = new FormData();
		progressLabelData.left = new FormAttachment(imageLabel, 0, SWT.CENTER);
		progressLabelData.top = new FormAttachment(progressBar, 0, SWT.BOTTOM);
		progressLabel.setLayoutData(progressLabelData);

		// Add the exit command
		exitCommand = new Command(mainShell, Command.EXIT, 0);
		exitCommand.setText("Exit");
		exitCommand.addSelectionListener(this);
		exitCommand.setDefaultCommand();
	}

	public void widgetSelected(SelectionEvent e) {
		if (e.widget == exitCommand) {
			destroy();
			main.exit();
		}
	}

	public void widgetDefaultSelected(SelectionEvent e) {}

	// Set the text of the progress label
	void setLabel(String label) {
		if (!progressLabel.isDisposed()) {
			progressLabel.setText(label);
			splashComposite.layout();
		}
	}

	// Set how many movies will be displayed. In practice it changes the
	// range of the progress bar
	void setMoviesCount(int count) {
		if (!progressBar.isDisposed()) {
			progressBar.setMaximum(count);
		}
	}

	// Advance the progress bar
	void advance() {
		if (!progressBar.isDisposed()) {
			progressBar.setSelection(progressBar.getSelection() + 1);
		}
	}

	void destroy() {
		exitCommand.dispose();
		splashComposite.dispose();
		if (appImage != null) {
			appImage.dispose();
		}
	}

}

BookingTicket

package moviebooking.moviedb;

/**
 * Simple class representing a booking ticket created by MovieDB in the 
 * sendBookingRequest method
 */
public class BookingTicket {

	private final double price;
	private final int code;

	BookingTicket(double price, int code) {
		this.price = price;
		this.code = code;
	}

	public int getCode() {
		return code;
	}

	public double getPrice() {
		return price;
	}
}

Movie

package moviebooking.moviedb;

import java.util.Vector;

/**
 * Simple class encapsulating a Movie instance
 */
public class Movie {

	private final String name;
	private final String imageLocation;
	private final String description;
	private final Vector showings;

	public Movie(String name, String imageLocation, String description, Vector showings) {
		this.name = name;
		this.imageLocation = imageLocation;
		this.description = description;
		this.showings = showings;
	}

	public String getName() {
		return name;
	}

	public String getDescription() {
		return description;
	}

	public String getImageLocation() {
		return imageLocation;
	}

	public Vector getShowings() {
		return showings;
	}


}

MovieDB

package moviebooking.moviedb;

import java.util.Vector;
import java.util.Hashtable;
import java.util.Calendar;
import java.util.Random;
import java.util.Date;
import java.io.InputStream;
import java.io.IOException;

/**
 * MovieDB: Gets access to the local/remote movie database For this
 * example we use a simple file to store the data A more elaborated
 * example would probably connect to a remote server
 */
public class MovieDB {

	private MovieDBListener listener;
	private int moviesCount = 3;
	private Vector movies = new Vector();
	private Hashtable props;

	public MovieDB(InputStream in) {
		props = new Hashtable();
		try 
		{
			StringBuffer sb = new StringBuffer();
			String key = null;
			String value = null;
			int chr = 0;
			while(chr != -1) {
				chr = in.read();
				if(chr == '=') {
					key = new String(sb).trim();
					sb = new StringBuffer();
				} else if(key != null && (chr == '\n' || chr == -1)) {
					value = new String(sb).trim();
					sb = new StringBuffer();
					props.put(key, value);
					key = null;
				} else {
					sb.append((char) chr);
				}
			}
		}
		catch (IOException e)
		{         
			e.printStackTrace();
		}
	}

	// set the listener
	public void setDBListener(MovieDBListener listener) {
		this.listener = listener;
	}

	// request MovieDB to get the data
	public void loadData() {
		// Create the database in a separate thread
		new Thread(new DBGenerator()).start();
	}

	// return a list of movies
	public Vector getMovies() {
		return movies;
	}

	public void close() {
		// do nothing. In a real case this should close any
		// DB or network connection
	}

	// request a Booking
	public void sendBookingRequest(String firstNameText,
			String familyNameText, Showing showing,
			int[][] selectedSeats) {
		// Simulate a request in a separate
		// thread
		new Thread(new ReservationRequest(selectedSeats, showing)).start();
	}

	// Sends an event to listeners
	private void sendEvent(int event, Object data) {
		if (listener != null) {
			listener.handleEvent(new MovieDBEvent(event, data));
		}
	}

	private final class DBGenerator implements Runnable {
		// use a set of fixed times for Showings
		private final int[] SHOWING_TIMES
		= new int[] {13, 15, 19, 22, 23};

		public void run() {
			try {
				synchronized (this) {
					sendEvent(MovieDBEvent.CONNECTING, "Local database..");
					//if (props != null) {
					if (props != null) {
						// Simulate a connection delay
						wait(500);
						moviesCount = Integer.parseInt((String)props.get("count"));
						// Inform how many movies are there
						sendEvent(MovieDBEvent.MOVIES_COUNT,
								new Integer(moviesCount));
						Random r = new Random();
						Date now = new Date();
						for (int i = 0; i < moviesCount; i++) {
							// Small delay per movie
							wait(1000);

							Vector showings = new Vector();
							// generate showings for 3 days from now
							final int DAYS_OF_SHOWINGS = 3;
							for (int k = 0; k < DAYS_OF_SHOWINGS; k++) {
								// and for each hour in SHOWING_TIMES
								for (int j = 0; j < SHOWING_TIMES.length; j++) {
									Calendar cal = Calendar.getInstance();
									cal.set(Calendar.HOUR_OF_DAY, 0);
									cal.set(Calendar.MINUTE, 0);
									cal.set(Calendar.SECOND, 0);
									cal.set(Calendar.MILLISECOND, 0);
									cal.set(Calendar.DAY_OF_MONTH, cal.get(Calendar.DAY_OF_MONTH) + k);
									try {
										// If this fails, this month doesn't have so many days 
										cal.getTime();
									} catch(IllegalArgumentException e) {
										//  Move to the next month 
										if(cal.get(Calendar.MONTH) == Calendar.DECEMBER) {
											cal.set(Calendar.MONTH, Calendar.JANUARY);											
										} else {
											cal.set(Calendar.MONTH, cal.get(Calendar.MONTH) + 1);
										}
										// Continue from the beginning of the next month. 
										cal.set(Calendar.DAY_OF_MONTH, DAYS_OF_SHOWINGS - k);
									}
									cal.set(Calendar.HOUR_OF_DAY, cal.get(Calendar.HOUR_OF_DAY) + SHOWING_TIMES[j]);
									// only add showings after now
									if (cal.getTime().getTime() > now.getTime()) {
										// the teather is a bit boring, always 14x20
										int rows = 14;
										int seatsPerRow = 20;
										boolean[][] seating = new boolean[rows][seatsPerRow];

										// Generate the seating
										for (int l = 0; l < rows; l++) {
											for (int m = 0; m < seatsPerRow; m++) {
												// one third of seats is occupied
												seating[l][m] = (Math.abs(r.nextInt() % 3) == 0);
											}
										}
										// give it some random price
										Showing show = new Showing(cal.getTime(), Math.abs(r.nextInt() % 100) * 10, seating);
										showings.addElement(show);
									}
								}
							}
							// Create movie based on the property file
							String movieKey = "movie" + i;

							Movie movie = new Movie((String)props.get(movieKey + ".name"),
									(String)props.get(movieKey + ".imageLocation"),
									(String)props.get(movieKey + ".description"),
									showings);
							movies.addElement(movie);
							sendEvent(MovieDBEvent.MOVIE_LOADED, new Integer(i));
						}
						sendEvent(MovieDBEvent.DATA_LOADED, null);
					} else {
						sendEvent(MovieDBEvent.DATA_ERROR, "No database found");
					}
				}
			} catch (InterruptedException e) {
				// Ignore, this shouldn't happen
			}
		}
	}

	// Internal class used to simulate a booking request in a separate
	// thread
	private final class ReservationRequest implements Runnable {
		private final int[][] seats;
		private final Showing showing;
		private final Random rnd = new Random();

		private ReservationRequest(int[][] seats, Showing showing) {
			this.seats = seats;
			this.showing = showing;
		}

		public void run() {
			try {
				synchronized (this) {
					sendEvent(MovieDBEvent.RESERVATION_REQUESTED, null);
					// add a small delay
					wait(1000);
					// the booking is always approved so generate the price
					// and an id code
					double price = showing.getPrice() * seats.length;            
					int code = Math.abs(rnd.nextInt());

					BookingTicket ticket = new BookingTicket(price, code);            
					sendEvent(MovieDBEvent.MOVIE_RESERVED, ticket);
				}
			} catch (InterruptedException e) {
				// just ignore
			}
		}
	}

}

MovieDBEvent

package moviebooking.moviedb;

/**
 * MovieDBEvent represents events triggered by MovieDB
 */
public class MovieDBEvent {

	public final static int CONNECTING = 1;
	public final static int DATA_LOADED = 2;
	public final static int MOVIES_COUNT = 3;
	public final static int MOVIE_LOADED = 4;
	public final static int MOVIE_RESERVED = 5;
	public final static int DATA_ERROR = 6;
	public final static int RESERVATION_REQUESTED = 7;

	private final int event;
	private final Object data;

	MovieDBEvent(int event, Object data) {
		this.event = event;
		this.data = data;
	}

	public int getEvent() {
		return event;
	}

	public Object getData() {
		return data;
	}
}

MovieDBListener

package moviebooking.moviedb;

/**
 * Entities interested in MovieDBEvents should implement this
 * interface
 */
public interface MovieDBListener {

	public void handleEvent(MovieDBEvent event);
}

Showing

package moviebooking.moviedb;

import java.util.Date;

/**
 * Simple class encapsulating a particular Showing of a Movie
 */
public class Showing {

	private final Date date;
	private final double price;
	// plan of seats. locations with value true are already booked
	private final boolean[][] seating;  

	public Showing(Date date, double price, boolean[][] seating) {
		this.date = date;
		this.price = price;
		this.seating = seating;
	}

	public Date getDate() {
		return date;
	}

	public boolean[][] getSeating() {
		return seating;
	}

	public double getPrice() {
		return price;
	}  

}