CIS 260 Assignment: Calculator II

The objective for this assignment is to be able to construct a calculator using the GUI components created in the previous assignment (with a couple of minor modifications). The calculator is designed to allow some minor precedence issues to be take care of. Tests should be done to make sure your calculator works properly before submitting it for grading.

Picture of working calculator

StackItem class

This calculator will have some limited ability to deal with operator precedence (such as doing multiplication and division before doing addition and subtraction). To do this, we will use a limited stack-type of data structure. Each item in the stack will be of type StackItem. The requirements for StackItem are as follows:

  1. There should be three public variables:
  2. You will need a constructor which takes three arguments and uses them to set (initialize) the three instance variables.
  3. [optional] A toString method would be useful for debugging purposes.

Calculator class instance variables

You should have the following instance variables (all private) for use in your Calculator class:

  1. a Hashtable<String,Integer> named hash which is used to translate from the String the display button returns into an integer that is easier to use
  2. a Vector<StackItem> named stack which is used to hold pending operations and results so the calculator can deal with precedence issues - although this is named stack, it is used in a manner in this program that is not entirely stack-like
  3. NumField display which is used to refer to the display in your calculator
  4. a couple of integer constants which are used to keep track of whether the calculator is expecting a new number (NEW_MODE), or if it is expecting additional digits added to an existing number (GATHER_MODE):
  5. an integer named state which is used to keep track of whether the calculator is in NEW_MODE or GATHER_MODE
  6. fourteen integer constants which are used to identify the current button being responded to:
  7. a boolean named error which will keep track of whether the calculator is in error mode (such as division by 0)

Calculator class methods

The Calculator class should extend the JFrame class. It should also implement ActionListener. There are eight methods you need to implement. They are as follows:

  1. public static void main(String[] args) - to get the calculator started
  2. public Calculator() - the constructor: creates GUI and inits instance variables
  3. private void reset() - used to clear the calculator back to initial conditions
  4. private void setDisplayValue(double n) - a convenience routine used to set the calculator's display with a calculated result
  5. private void resolveStack(StackItem itm) - used to perform any pending operations on the stack (needed for precedence to work)
  6. public void actionPerformed(ActionEvent e) - handles button clicks from keypad (actually, it just passes the job off to handleButton()); this method is required to be an ActionListener
  7. private void handleButton(int src) - this is where all the button clicks are handled; this routine and resolveStack perform almost all of the logic to make the calculator function properly
  8. private void initHashtable() - this routine just sets up the hashtable for later use; it is used in the ActionListener to translate the keypad's Strings into integers that identify which button was clicked

The main(String[] args) method

Create a new Calculator object.

The Calculator constructor

This constructor assembles the GUI and initializes the instance variables.

  1. call initHashtable()
  2. create a new Vector<StackItem> to use as a stack
  3. set the look-and-feel of the GUI if desired
  4. set the layout manager you want to use
  5. create a Keypad and add it to the Calculator
  6. create a display (NumField) and add it to the display
  7. set the Calculator to exit when the user chooses to close the JFrame
  8. add ourself as the ActionListener for the keypad
  9. call reset()
  10. set the size of the Calculator and make it visible

The reset() method

This method is pretty short and easy.

  1. set the state to NEW_MODE
  2. set the error indicator variable to false
  3. clear the stack
  4. use the display's clear() method to clear the display

The setDisplayValue(double n) method

This method is simple.

  1. use the display's setValue method to display the argument n
  2. set the state to NEW_MODE (the calculator is expecting a new number to be entered)

The resolveStack(StackItem itm) method

This method handles pending operations. For example, if the user enters "5 + 2 =", the "5 +" must be stored on the stack so it can be used when the "=" is encountered (the 2 will still be in the display). The Calculator will actually push the "2 =" onto the stack as well and then resolve the stack calculations. Here's what it should do:

  1. add the argument sent to the start of the stack
  2. while the stack has more than one item and the precedence of the second item is greater than or equal to the precedence of the first item, do the following:
  3. set the state to NEW_MODE

The actionPerformed(ActionEvent e) method

This method creates a thread to be run at Swing's convenience. The thread just finds out what button was clicked, translates that into an identifying integer, and then calls handleButton.

  1. first, store the value of argument e in a new constant local variable named event
  2. use the SwingUtilities.invokeLater method to run a thread which does the following:

The handleButton(int src) method

This is the method that has to figure out what to do for each button click. It is likely to be the longest method in your Calculator application. Here's what you have to do:

  1. if error is true and src is not equal to CLEAR or CLEAR_ENTRY, then return
  2. if src is equal to CLEAR, then call reset()
  3. if src is equal to CLEAR_ENTRY, then set error to false and clear the display
  4. if src is equal to ADD or SUBTRACT, then call resolveStack with a new StackItem where the value is the display's value, the operator is src, and the precedence is 1
  5. if src is equal to MULTIPLY or DIVIDE, then call resolveStack with a new StackItem where the value is the display's value, the operator is src, and the precedence is 2
  6. if src is equal to EQUAL, then call resolveStack with a new StackItem where the value is the display's value, the operator is src, and the precedence is 0; then clear the stack
  7. if src is equal to CHANGE_SIGN, then use setDisplayValue to set the display's value to: -1 * the current value in the display
  8. if src is equal to SQUARE_ROOT, then use setDisplayValue to set the display's value to the square root of the current value in the display [you might want to use Math.pow(value, .5) for this]
  9. if src is equal to PERCENT and state equals GATHER_MODE and the stack has at least one element in it, then use setDisplayValue to set the display's value to the number stored in the first stack item * the display's current value divided by 100.0
  10. if src is equal to RECIPROCAL and the display's value is not 0, then use setDisplayValue to set the display's value to 1.0 divided by the current value shown in the display
  11. if src is equal to RECIPROCAL and the display's value is 0, then call reset(), set error to true, and and send the message ""Can not divide by 0"" to the display using its setError method
  12. if src is equal to BACKSPACE and the state is GATHER_MODE and the text currently shown in the display has at least one character in it, then use the display's setText method to display the same text with the last character missing [you probably want to use the String object's substring method for this]
  13. if src is equal to 0 through 10, then do the following:

The initHashtable() method

The purpose of this method is to create the hashtable and populate it with all of the possible Strings returned from the keypad. Each entry also associates a unique integer value used to identify the String, and therefore the button that was clicked.

  1. create the hashtable as a new Hashtable<String,Integer>
  2. use the hashtable's put method to enter all ten digit strings, where the String "0" is entered with the integer 0, etc.
  3. use the hashtable's put method to enter all the rest of the possible Strings returned from the keypad and their associated identification number [eg. hash.put("Backspace", BACKSPACE);]
  4. Keep in mind that the identifying numbers are stored as constants which should be defined already as instance variables.