This class takes care of displaying the distribution of seats in the theater. It shows the layout of the movie threatre from above. The occupied seats are shown as red and the available seats as blue. The user can use the arrow keys to select the desired seat position to reserve. The currently selected seats are shown in green. The screen of theatre is presented by a gray rectangle. The screenshot below gives an idea of how it all looks like.
SeatingCanvas
is implemented as a simple custom
widget which implements its own drawing hand key event handling. This is achieved
by utilizing the Canvas
widget.
Create the SeatingCanvas
class
file.
Import the
required classes and assign this class to the package moviebooking.ui
.
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;
Place 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 we are going to paint every pixel ourselves in the paint listener (paintControl
method).
For more information the Shell
class, see the class 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; } }
Add listeners. PaintListener()
is set to handle events generated when controls need to be painted. When
this happens, the paintControl()
method is invoked. KeyListener()
is set to handle events generated when keys are pressed on the device keyboard. ControlListener()
is set to handle events generated when controls are moved
or resized. Note that the methods invoked by these listeners are defined in
step 5 below
// 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); }
Draw the theatre seats and environment.
This is an expensive operation as there
are quite many shapes to draw and our implementation redraws all of them every
time that SeatingCanvas
needs to be painted. This leads
to some flickering on the screen and an unpleasant drawing sequence. A solution
to this problem is to use double buffering. This means that the content is
first drawn into a buffer which is then drawn to the screen. This way all
the shapes drawn appear on the screen simultaneously instead of little by
little.
In eSWT this is done 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.
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); } }
In the code above, the paintControl(PaintEvent)
method
of the PaintListener()
interface is invoked when a control needs to be painted. This
also defines the details of the paintEvent
.
Add rules for events.
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 }
Apply the
controls and drawing methods. The selection moves when the user presses the
keys. When the control's size changes, we dispose the double buffer Image
and
call redraw()
to make the paint listener paint again.
In the paintControl
method the double buffer Image
is
then recreated with the new size of the control.
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; } }
The SeatingScreen
class creates a simple, secondary
shell containing the SeatingCanvas
.
Create the SeatingScreen
class
file.
Import the
required classes and assign this class to the package moviebooking.ui
.
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 containsthe SeatingCanvas */ 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
.
For more information the Shell
class, see the class 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"); 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 commands.
For more information on the Command
class, see the class Command
.
// 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); } }