This section provides the source code for the MovieBooking example.
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() { } }
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; } } } }
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(); } } }
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; } } }
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; } } }
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); } }
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(); } } }
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; } }
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; } }
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 } } } }
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; } }
package moviebooking.moviedb; /** * Entities interested in MovieDBEvents should implement this * interface */ public interface MovieDBListener { public void handleEvent(MovieDBEvent event); }
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; } }