The SeatingCanvas
class provides the UI that displays
the distribution of seats in the theater. It shows the layout of the
movie theater from above: the occupied seats are shown as red, the
available seats as blue, and the theater screen as a gray rectangle.
The user can use the touch screen or the arrow keys to select which
seat position to reserve (the currently selected seats are shown in
green).
The SeatingCanvas
class creates the following
UI:
Figure: Seat selection screen
The SeatingCanvas
class is implemented as a simple
custom widget, which implements its own drawing hand key event handling
by utilizing the Canvas
widget.
To implement the seat selection screen:
Create the SeatingCanvas.java
class file.
Import the required classes.
import com.nokia.example.moviebooking.moviedb.Showing; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Shell;
Set SeatingCanvas
to extend Canvas
and implement PaintListener
, ControlListener
, KeyListener
, and MouseListener
, and create
the required variables.
class SeatingCanvas extends Canvas implements PaintListener, ControlListener, KeyListener, MouseListener { 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; private Point seatSize; private Rectangle screenRectangle;
Place the SeatingCanvas
inside a Shell
whose parent is the top-level Shell
. This kind of a second-level Shell
can be thought of as a dialog or a window on top of the parent Shell
. The NO_BACKGROUND
flag is used to
prevent the Canvas
from drawing its background, since
the MIDlet uses the paint listener's paintControl
method to paint every pixel.
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(); }
Use the init
method to define the listeners the class needs.
void init() { // 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 for touch events addMouseListener(this); // Listen if the size of the parent changes shell.addControlListener(this); }
Implement the key event handlers for key-based interaction.
public void keyPressed(KeyEvent e) { // Nothing to do, we are only interested in key releases } public void keyReleased(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(); } } }
Draw the theatre seats and environment.
Drawing the theatre seats
and the environment is an expensive operation, as there are various
shapes to draw and the MIDlet implementation redraws all of them every
time the SeatingCanvas
needs to be painted. This
leads to some flickering on the screen and an unpleasant drawing sequence.
To solve the problem, use double buffering. This means that the
content is first drawn into a buffer, which is then drawn to the screen.
Double buffering ensures that all the shapes appear on the screen
simultaneously, instead of little by little. In eSWT, double buffering
is implemented by creating an off-screen image size and a GC
object that allows drawing onto the image.
After that the normal drawing methods can be called and applied to
the image.
In the following code snippet, the paintControl(PaintEvent)
method of the PaintListener
interface is invoked when a Control
needs to be painted. The interface also defines
the details of the PaintEvent
. When the size of the Control
changes, the MIDlet disposes the double buffer Image
and calls redraw
to
make the paint listener paint again. In the paintControl
method, the double buffer Image
is then recreated
with the new Control
size.
public void paintControl(PaintEvent e) { // Dump the offscreen image onto the canvas drawSeating(); e.gc.drawImage(doubleBuffer, 0, 0); } public void controlMoved(ControlEvent e) { // We are not interested in this event } public void controlResized(ControlEvent e) { // 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; } } // Drawing of the offscreen image private void drawSeating() { 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); } Rectangle screen = getScreen(); // Clear the image /*Point canvasSize = new Point(shell.getClientArea().width, shell.getClientArea().height); */ Point canvasSize = getSize(); gc.setBackground(bgColor); gc.fillRectangle(0, 0, canvasSize.x, canvasSize.y); gc.setForeground(frameColor); gc.drawRectangle(1, 1, canvasSize.x - 2, canvasSize.y - 2); gc.setForeground(screenColor); gc.setBackground(screenColor); gc.fillRectangle(screen); boolean[][] seats = showing.getSeating(); int rows = seats.length; int seatsPerRow = seats[0].length; int radius = getRadius(rows, seatsPerRow); // Space per seat/row Point seatSize = getSeatSize(rows, seatsPerRow); // Empty space to leave for horizontal center alignment int xOffset = getOffsetX(screen, seatsPerRow, seatSize); 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 * seatSize.x, (screen.y + screen.height + radius / 2) + i * seatSize.y, radius, radius); } } for (int i = 0; i < selected.length; i++) { gc.setBackground(selectedSeatColor); System.out.println("selected[i][1]" + selected[i][1]); System.out.println("selected[i][0]" + selected[i][0]); gc.fillOval(screen.x + xOffset + selected[i][1] * seatSize.x, (screen.y + screen.height + radius / 2) + selected[i][0] * seatSize.y, radius, radius); } } private int getOffsetX(Rectangle screen, int seatsPerRow, Point seatSize) { int xOffset = (screen.width - seatSize.x * seatsPerRow) / 2; return xOffset; } private Rectangle getScreen() { Point canvasSize = getSize(); int width = (95 * canvasSize.x) / 100; int height = canvasSize.y / 20; screenRectangle = new Rectangle( (canvasSize.x - width) / 2, height, width, height); return screenRectangle; } /** * Calculate the seat per size/row * * @param canvasSize * @param screen * @param rows * @param seatsPerRow * @param radius * @return */ private Point getSeatSize(int rows, int seatsPerRow) { int radius = getRadius(rows, seatsPerRow); Rectangle screen = getScreen(); int spacex = screen.width / seatsPerRow; int spacey = ((getSize().y - (screen.height + screen.y + radius / 2)) / rows); seatSize = new Point(spacex, spacey); return seatSize; } private int getRadius(int rows, int seatsPerRow) { Rectangle screen = getScreen(); int radius = Math.min((screen.width / seatsPerRow), ((getSize().y - (screen.height + screen.y)) / rows)) - 2; return radius; }
Implement the mouse event handlers for touch interaction.
public void mouseDoubleClick(MouseEvent event) { // Nothing to do } public void mouseDown(MouseEvent event) { handleMouse(event); } public void mouseUp(MouseEvent event) { handleMouse(event); } private void handleMouse(MouseEvent event) { Rectangle screen = getScreen(); Point seatSize = getSeatSize(seats.length, seats[0].length); if (event.y > (screen.y + screen.height)) { int col = (event.x) / seatSize.x; if (col < 1) { col = 1; } if (col > seats[0].length) { col = seats[0].length; } int row = (event.y - screen.y) / seatSize.y; if (row < 1) { row = 1; } if (row > seats.length) { row = seats.length; } for (int seat = 0; seat < seatCount && col < seatsPerRow; col++) { if (!seats[row - 1][col - 1]) { selected[seat][0] = row - 1; selected[seat][1] = col - 1; seat++; } } redraw(); } } }
Create the SeatingScreen.java
class file.
The SeatingScreen
class is used to create a simple, secondary Shell
containing the SeatingCanvas
.
Import the required classes.
import com.nokia.example.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.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Shell;
Set SeatingScreen
to implement SelectionListener
and ControlListener
, and create the required variables.
class SeatingScreen implements SelectionListener, ControlListener { private Command exitCommand, okCommand, backCommand; private MovieBooking main; private Shell seatingShell; private Shell mainShell; private SeatingCanvas seatingCanvas;
Create the Shell
.
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"); // 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()); updateSize();
Define the commands for the screen and the methods to handle the commands.
// 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); seatingShell.open(); } 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) { updateSize(); } private void updateSize() { // Set the same size as the parent seatingShell.setBounds(mainShell.getBounds()); } }