©1994-2003 Kevin Boone
Home     Section index     K-Zone home Simple Java programs

Site search

Glossary
Confused by computer jargon? Look it up!

Shameless plug


Now available!

Articles
- Ten-minute guide to setting up a WAP site

- Talk like your boss: new developments in managerese

More...

Development
File handling in the Linux kernel

Java development for the Sony-Ericsson P800

SunONE Application Server 7 FAQ

More...

Linux
Using Linux with the Treo 600

- Linux on the Tecra M1

- Some notes on openzaurus

More...

Download
Java stuff

Linux stuff

More...

(Please read the download policy)

Home automation
The X10 system

Linux TW723 driver

More...

The K-Zone
K-Zone computing

K-Zone law

K-Zone education and science

K-Zone motorcycles

K-Zone DIY

K-Zone railways

K-Zone martial arts

About the author

K-Zone home page

 
Software development
Computing
Hello1.java
Add1.java
Add1_1.java
AwtTest.java
Button1.java
Button2.java
CalculatorApp.java
ClockApplet.java
CountSpace1.java
DateTime.java
Div1.java
DoNothing.java
Factorial1.java
Factorial2.java
FontChooser.java
IfTest.java
Inheritance1.java
Loan.java
DiceThrower.java
MultiplicationTable.java
Queue.java
Reverse1.java
SoundApplet.java
Stressometer.java
TextReaderTest.java
WebMerge.java
Zener.java
`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"); } }