|
|
`Calculator' example program: an applet that implements a simple, 4-function calculator
Calculator.java
/*==============================================================================
Calculator.java
This program simulates the simple `four-function' calculator (the
sort that can only add, divide, subtract and multiply). Your
will see that although this appears very simple, in fact we
need quite an extensive program to implement it.
The applet simulates the buttons and display of the standard
pocket calculator, and should look like this when executed:
(If you aren't looking at the HTML version of this file, you won't see
the picture)
To signal that the user has tried to do something that is mathematically
impossible (specifically to divide something by zero) this program
uses the mechanism of `exception throwing'.
The issue of exception handling is covered in more detail later in the
course; don't worry if it doesn't make much sense at the moment.
Kevin Boone, July 1999
==============================================================================*/
import java.applet.Applet;
import java.awt.*;
// We must include java.awt.event.*, as this includes definitions of the
// event handling classes, like ActionEvent
import java.awt.event.*;
/*==============================================================================
Calculator class
The Calculator class provides most of the functionality for this program. After
the definition of Calculator we will define a class called `DivideByZeroException'
This is a very simple class that will be used to indicate a division-by-zero
error condition
==============================================================================*/
/// QUESTION: why am I specifying `implements ActionListener, KeyListener'
/// to this class?
public class Calculator extends Applet implements ActionListener, KeyListener
{
/*==============================================================================
Attributes of Calculator class
==============================================================================*/
// MAX_INPUT_LENGTH is a constant indicating how large the number entered by
// the user can be. A Java `double' can store a value with a precision of
// about 17 digits. Therefore there is no point in allowing the user to
// enter numbers any bigger than this.
final int MAX_INPUT_LENGTH = 17;
/*
Display modes
The attribute `displayMode' can take one of three values. For ease of reading
I have defined three constants to represent these values. It doesn't matter
at all what the values of these constants are (I have used 0, 1 and 2), as
long as they are all different.
The displayMode attribute indicates what is currently being displayed. It
will either be the number being entered by the user (INPUT_MODE), the
result of the last calculation (RESULT_MODE) or an error message
(ERROR_MODE). This attribute is very important to the correct function of
the program. For example, if the user types a digit when the program is
in INPUT_MODE, the program should append the digit to the number
currently in the display. If in RESULT_MODE or ERROR_MODE, the display
should be cleared and the new digit put at the start. In fact,
many of the operations in this class behave differently according to the
value of displayMode
*/
/// QUESTION: why have I defined constants for these values? In the program
/// I write `if (displayMode == ERROR_MODE)'; why don't I just write
/// `if (displayMode == 2)' ?
final int INPUT_MODE = 0;
final int RESULT_MODE = 1;
final int ERROR_MODE = 2;
int displayMode;
// displayLabel is the area of the applet in which all display will be shown.
// I have used a Java `Label' object for this.
Label displayLabel;
// clearOnNextDigit is set to `true' whenever an operator (`+', `-', etc) is
// typed. This causes the next digit entered to be the start of a new number.
boolean clearOnNextDigit;
// lastNumber is the number that was entered by the user before the last operator.
// for example, if the user type `2' followed by `+', then lastNumber will be
// set to `2'
double lastNumber;
// lastOperator is the operator last typed by the user. If the user types a
// `+' sign (or clicks on the `+' button), then lastOperator is set to
// '+'. This attribute's value is set to zero to indicate that no operator
// has been typed yet
char lastOperator;
/*==============================================================================
Calculator constructor
This operator (as with all constructors) is executed when the new object
is created. In this case we create all the buttons (and the panels that
contain them), then reset the calculator
The layout of buttons in this Applet is quite complex. We have three groups
of buttons, one for digits, one for operators and one for `controls'
(`=', `AC' and `C'). I'm not sure `controls' is the right word here, but
I couldn't think of a better one. So we create three `Panel' objects
to contain each group of buttons. A Panel is a blank screen area whose
job is simply to hold other objects. Associated with each Panel is a
layout manager. The job of the layout manager is to group the buttons in
the right order. For example, the button panel has a grid of buttons, with
four rows of three buttons like this:
7 8 9
4 5 6
1 2 3
. 0 +/-
So we set the layout manager for this panel to a new GridLayout object.
When we create the new GridLayout we specify `new GridLayout(4, 3)'
which means `create a new GridLayout with four rows of three columns'.
Having created the panels, we add the buttons. Finally we add all the
panels into a single panel (called `buttonPanel') and add this to the
applet. We also add the display area.
==============================================================================*/
public Calculator()
{
super();
setLayout (new BorderLayout());
Panel buttonPanel = new Panel();
Panel numberPanel = new Panel();
numberPanel.setLayout(new GridLayout(4, 3));
addButtonToPanel (numberPanel, new Button("7"), Color.blue);
addButtonToPanel (numberPanel, new Button("8"), Color.blue);
addButtonToPanel (numberPanel, new Button("9"), Color.blue);
addButtonToPanel (numberPanel, new Button("4"), Color.blue);
addButtonToPanel (numberPanel, new Button("5"), Color.blue);
addButtonToPanel (numberPanel, new Button("6"), Color.blue);
addButtonToPanel (numberPanel, new Button("1"), Color.blue);
addButtonToPanel (numberPanel, new Button("2"), Color.blue);
addButtonToPanel (numberPanel, new Button("3"), Color.blue);
addButtonToPanel (numberPanel, new Button("."), Color.blue);
addButtonToPanel (numberPanel, new Button("0"), Color.blue);
addButtonToPanel (numberPanel, new Button("+/-"), Color.blue);
buttonPanel.add(numberPanel);
Panel operatorPanel = new Panel();
operatorPanel.setLayout(new GridLayout(4, 1));
addButtonToPanel (operatorPanel, new Button("/"), Color.green);
addButtonToPanel (operatorPanel, new Button("*"), Color.green);
addButtonToPanel (operatorPanel, new Button("-"), Color.green);
addButtonToPanel (operatorPanel, new Button("+"), Color.green);
buttonPanel.add(operatorPanel);
Panel controlPanel = new Panel();
controlPanel.setLayout(new GridLayout(4, 1));
addButtonToPanel (controlPanel, new Button("AC"), Color.cyan);
addButtonToPanel (controlPanel, new Button("C"), Color.cyan);
addButtonToPanel (controlPanel, new Button("="), Color.cyan);
buttonPanel.add(controlPanel);
displayLabel = new Label("");
displayLabel.setAlignment(Label.CENTER);
/// QUESTION: what does `North' mean here?
add(displayLabel, "North");
displayResult(0);
add(buttonPanel);
addKeyListener(this);
requestFocus();
// clearAll sets the attributes of the Calculator object to
// initial values (this is the operation that is called
// when the user clicks on the `AC' button
clearAll();
}
/*==============================================================================
setDisplayString
This operation sets the text in the display area to the specified string.
This hardly needs to be an operation at all, as it is so simple. However,
it is called many many other operations, and using an operation for this
function would make it much easier to modify the program if we chose a
different way to display data. Otherwise, a large number of lines would
have to be modified.
==============================================================================*/
void setDisplayString(String s)
{
displayLabel.setText(s);
}
/*==============================================================================
getDisplayString
This operation gets the text currently in the display area. See the note for
`getDisplayString' above for details.
==============================================================================*/
String getDisplayString ()
{
return displayLabel.getText();
}
/*==============================================================================
clearAll
Sets the state of the calculator to the `just switched on' state, that is,
`0' in the display, ready to input digits, and the last operator equal to
zero (that is, there is no last operator). This operation is called by the
constructor to initialize the Calculator, and whenever the user
clicks on `AC'
==============================================================================*/
void clearAll()
{
setDisplayString("0");
lastOperator = 0;
lastNumber = 0;
displayMode = INPUT_MODE;
clearOnNextDigit = true;
}
/*==============================================================================
clearLastEntry
Clears the number currently in the display. This is called when the user
clicks on the `C' button.
==============================================================================*/
void clearLastEntry()
{
setDisplayString("0");
clearOnNextDigit = true;
displayMode = INPUT_MODE;
}
/*==============================================================================
displayResult
Displays the specified number in the display area, and sets the attributes to
indicate that a result is being displayed. Specifically this operation sets
`clearOnNextDigit' to `true'. This means that as soon as the user types
a digit, the display will be cleared to make space for a new number.
==============================================================================*/
void displayResult(double result)
{
setDisplayString(Double.toString(result));
lastNumber = result;
displayMode = RESULT_MODE;
clearOnNextDigit = true;
}
/*==============================================================================
displayError
Displays the specified error message in the display area, and sets the
attributes to indicate that an error message is being displayed. Specifically
this operation sets `clearOnNextDigit' to `true'. This means that as soon
as the user types a digit, the display will be cleared to make space
for a new number.
The displayMode is set to ERROR_MODE to indicate that the display contains an
error message and not a number. This is important because the user might
press a button that modifies the current number (like `+/-'). Clearly
we can't change the sign of an error message (or take its square root, etc).
==============================================================================*/
void displayError(String errorMessage)
{
setDisplayString(errorMessage);
lastNumber = 0;
displayMode = ERROR_MODE;
clearOnNextDigit = true;
}
/*==============================================================================
addButtonToPanel
This is a `convenience function', that is, it exists simply to reduce the
number of lines in the program (and therefore make it more convenient for the
programmer to manage). This operation is called by the constructor to add
a new button to the display. It sets the buttons colour, and sets the
key listener and action listener to the appropriate operations (this has
to be done for every button, as we can't predict which button will have
the input focus at any given time).
==============================================================================*/
void addButtonToPanel(Panel panel, Button button, Color backgroundColour)
{
panel.add(button);
button.setBackground(backgroundColour);
button.addKeyListener(this);
button.addActionListener(this);
}
/*==============================================================================
actionPerformed
This operation is called whenever the user clicks a button. This happens
because the operation `addButtonToPanel' calls `addActionListener' for
every button. All this operation does is pass on the text on the button to
`processButton'
==============================================================================*/
public void actionPerformed (ActionEvent e)
{
processButton(e.getActionCommand());
}
/*==============================================================================
processButton
This operation takes action according to the user's input. It is called from
two other operations: actionPerformed (when user clicks a button) and
keyPressed (when the user presses a key on the keyboard). This operation
examines the text on the button (the parameter `command') and calls the
appropriate operation to process it.
==============================================================================*/
void processButton(String command)
{
if (command.equals("0")) addDigit(0);
if (command.equals("1")) addDigit(1);
if (command.equals("2")) addDigit(2);
if (command.equals("3")) addDigit(3);
if (command.equals("4")) addDigit(4);
if (command.equals("5")) addDigit(5);
if (command.equals("6")) addDigit(6);
if (command.equals("7")) addDigit(7);
if (command.equals("8")) addDigit(8);
if (command.equals("9")) addDigit(9);
if (command.equals(".")) addDecimalPoint();
if (command.equals("*")) processOperator('*');
if (command.equals("-")) processOperator('-');
if (command.equals("/")) processOperator('/');
if (command.equals("+")) processOperator('+');
if (command.equals("=")) processEquals();
if (command.equals("+/-")) processSignChange();
if (command.equals("AC")) clearAll();
if (command.equals("C")) clearLastEntry();
}
/*==============================================================================
getNumberInDisplay
Returns a double value indicating the number in the display. This operation
should never be called if the display does not contain a number. When an
error message is being displayed, the value of `displayMode' is ERROR_MODE,
so this can be used to test whether the display contains a number.
This operation is only necessary to make it easier in future if we decide to
represent data on the display in a different way (i.e., to use a different
object rather than `Label')
==============================================================================*/
double getNumberInDisplay()
{
String input = displayLabel.getText();
return Double.parseDouble(input);
}
/*==============================================================================
processLastOperator
Carries out the arithmetic operation specified by the last operator, the last
number and the number in the display.
If the operator causes an error condition (i.e., the user tries to divide
something by zero), this operation can throw an execption.
==============================================================================*/
/// QUESTION: if the user clicks on the buttons `2', `+' and `3', what values
/// will found in the attributes `lastOperator' and `lastNumber', and the
/// variable `numberInDisplay'?
double processLastOperator() throws DivideByZeroException
{
double result = 0;
double numberInDisplay = getNumberInDisplay();
switch (lastOperator)
{
case '*':
result = lastNumber * numberInDisplay;
break;
case '+':
result = lastNumber + numberInDisplay;
break;
case '-':
result = lastNumber - numberInDisplay;
break;
case '/':
if (numberInDisplay == 0)
throw (new DivideByZeroException());
result = lastNumber / numberInDisplay;
break;
}
return result;
}
/*==============================================================================
processOperator
Processes the operator most recently typed. All we do is evalute the
_last_ operator typed (not this one), and store this current attribute
is `lastOperator' so it will get processed when the user types another
operator, or `='.
==============================================================================*/
void processOperator(char op)
{
if (displayMode != ERROR_MODE)
{
double numberInDisplay = getNumberInDisplay();
if (lastOperator != 0)
{
try
{
double result = processLastOperator();
displayResult(result);
lastNumber = result;
}
catch (DivideByZeroException e)
{
displayError("Division by zero!");
}
}
else
{
lastNumber = numberInDisplay;
}
clearOnNextDigit = true;
lastOperator = op;
}
}
/*==============================================================================
processEquals
Deals with the user clicking the `=' button. This operation finishes the
most recent calculator. If the displayMode is `ERROR_MODE' (that is, the
display contains an error message) this operation should not do anything.
==============================================================================*/
void processEquals()
{
if (displayMode != ERROR_MODE)
{
try
{
double result = processLastOperator();
displayResult(result);
}
catch (DivideByZeroException e)
{
displayError("Division by zero!");
}
lastOperator = 0;
}
}
/*==============================================================================
processSignChange
This operation is called when the user clicks on the `+/-' (sign change)
button. If the number in the display is negative it is converted to positive.
If the number in the display is positive it is converted to negative. If
the number in the display is zero, nothing happens (as `-0' is meaningless).
This operation behaves slightly differently depending on what mode the
display is in. If the user is currently entering a number, then it looks
at the _string_ in the display. If it is displaying a result it looks at
the _number_ in the display.
==============================================================================*/
/// QUESTION: Why is this? Why does this operation have to behave differently
/// depending on whether a result or a new number is in the display? Hint: it
/// is quite a subtle problem. The program would work correctly most of the
/// time if we always treated the display as a number.
void processSignChange()
{
if (displayMode == INPUT_MODE)
{
String input = getDisplayString();
if (input.length() > 0)
{
if (input.indexOf("-") == 0)
setDisplayString(input.substring(1));
else
setDisplayString("-" + input);
}
}
else if (displayMode == RESULT_MODE)
{
double numberInDisplay = getNumberInDisplay();
if (numberInDisplay != 0)
displayResult(-numberInDisplay);
}
// If displayMode is `ERROR_MODE' then this operation should have no
// effect (because you can't change the sign of an error message!)
}
/*==============================================================================
addDigit
This operation is called when the user clicks a digit button, or types a
digit key. If the number currently being entered is less than 17 digits long,
then it adds the new digit to the end of the display.
==============================================================================*/
void addDigit(int digit)
{
/// QUESTION: what are the next two lines for?
if (clearOnNextDigit)
setDisplayString("");
// We have to be careful to prevent the user entering ugly numbers
// like `000'. If the number in the display is `0', and the user
// clicks on `0', this display is not changed.
String inputString = getDisplayString();
if ((!inputString.equals("0") || digit > 0) && inputString.length() < MAX_INPUT_LENGTH)
{
setDisplayString(inputString + digit);
}
displayMode = INPUT_MODE;
clearOnNextDigit = false;
}
/*==============================================================================
addDecimalPoint
Called when the user clicks on the decimal point button. Puts a decimal
point on the end of the number currently being entered. If the number
already contains a decimal point, this operation should do nothing
(that is, it should be impossible to enter a number like `1.2.3'
==============================================================================*/
void addDecimalPoint()
{
displayMode = INPUT_MODE;
if (clearOnNextDigit)
setDisplayString("");
String inputString = getDisplayString();
// If the input string already contains a decimal point, don't
// do anything to it.
if (inputString.indexOf(".") < 0)
setDisplayString(new String(inputString + "."));
}
/*==============================================================================
keyPressed
This operation is called when the user presses a key, when any button has the
input focus. This happens because the operation `addButtonToPanel' calls
`addKeyboardListener' for each button that it knows about. However,
this operation does nothing in this program, as keyboard input is handled
in the operation `keyTyped'. Because this applet is defined to `implement
KeyEventHandler' we have to provide this operation, even if it does
nothing.
==============================================================================*/
public void keyPressed(KeyEvent e)
{
}
/*==============================================================================
keyReleased
Called automatically whenever the user presses and then releases a key. See
note for `keyPressed' for details
==============================================================================*/
public void keyReleased(KeyEvent e)
{
}
/*==============================================================================
keyTyped
This operation is called when the user presses a key, and then releases it
when any button has the input focus. This happens because the operation
`addButtonToPanel' calls `addKeyboardListener' for each button that it knows
about.
This operation converts the key press to the text of the corresponding button,
then passes it to processButton to process. For example, if the user
presses `enter', this should have the same effect as clicking `='. So this
operation calls `processButton' with the text `='. If the user
presses `escape' this should be treated the same as the `C' button. All other
keys are treated as digits. If the user presses a letter rather than
a digit, it will still be passed to `processButton', but processButton
will ignore it.
==============================================================================*/
public void keyTyped(KeyEvent e)
{
String command;
char keyChar = e.getKeyChar();
if (keyChar == KeyEvent.VK_ENTER)
{
command = new String("=");
}
else if (keyChar == KeyEvent.VK_ESCAPE)
{
command = new String("C");
}
else
{
/// QUESTION: what do these next two lines do?
byte bytes[] = {(byte)keyChar};
command = new String(bytes);
}
processButton(command);
}
}
/*==============================================================================
DivideByZeroException class
This class is used to indicate that the user has tried to divide a number
by zero (which is, of course, impossible. The Calculator applet should
respond by displaying an error message
The issue of exception handling is covered in more detail later in the
course; don't worry if it doesn't make much sense at the moment.
==============================================================================*/
class DivideByZeroException extends Exception
{
DivideByZeroException()
{
super("Divide by zero");
}
}
|