package Calculator; /* * Event-driven ButtonHandler */ public class ButtonHandler implements Runnable { // components private Calculator calc; private Thread spirit; // implicit state private double arg1, arg2; // arguments of the operator private int op = Calculator.EQUALS; // the operator last pressed // explicit state (i.e., corresponding to the circles in the state diagram) // curState holds the current state. At end the end of each iteration, // the value of curState is changed to the next state. private int curState = START_STATE; // constants for providing understandble names to the states private static final int ARG1_STATE = 0; // waiting for 1st operand private static final int AFTER_OP_STATE = 1; // right after op private static final int ARG2_STATE = 2; // waiting for 2nd operand private static final int AFTER_EQUALS_STATE = 3;// right after = private static final int START_STATE = ARG1_STATE; // start in ARG1_STATE // constructor public ButtonHandler( Calculator c ) { this.calc = c; reset(); // initialize the state by resetting the calculator this.spirit = new Thread( this ); this.spirit.start(); } // This method returns the calculator to a default initial state private void reset() { arg1 = 0; arg2 = 0; op = Calculator.CLEAR; calc.setText( "" ); curState = START_STATE; } /** * run() contains a while( true ) loop which switches on * the button pressed, and then dispatches to appropriate * event handler methods. */ public void run() { while ( true ) { printCurState(); // for debugging // get input int button = calc.getButton(); printButton( button ); // for debugging switch( button ) { case Calculator.CLEAR: handleClear(); break; case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 0: case Calculator.DOT: // button is a digit or decimal point handleDigitOrDot( button ); break; case Calculator.OP_ADD: case Calculator.OP_SUB: case Calculator.OP_MUL: case Calculator.OP_DIV: // button is an operator handleOp( button ); break; case Calculator.EQUALS: handleEquals(); break; } } } /* * state-dependent event-driven behavior rules */ /** This is called when Clear is pressed. It resets * the calculator no matter what the current state is. */ private void handleClear() { reset(); } /** This is called when a Digit or Dot is pressed. */ private void handleDigitOrDot( int button ) { switch( curState ) { case ARG1_STATE: // update the screen and arg1 accordingly. appendDigitOrDot( button ); // updates the screen arg1 = getNumFromScreen(); // updates arg1 curState = ARG1_STATE; break; case AFTER_OP_STATE: // If the previous button pressed was an op button // then this digit starts arg2. calc.setText( "" ); // clear screen to start a new number appendDigitOrDot( button ); arg2 = getNumFromScreen(); curState = ARG2_STATE; break; case ARG2_STATE: // update the screen and arg2 accordingly. appendDigitOrDot( button ); arg2 = getNumFromScreen(); // updates the screen curState = ARG2_STATE; // updates arg2 break; case AFTER_EQUALS_STATE: // If the previous button pressed was an op button // then this digit starts arg1. calc.setText( "" ); // clear screen to start new answer appendDigitOrDot( button ); arg1 = getNumFromScreen(); curState = ARG1_STATE; break; default: System.out.println( "Invalid State!" ); reset(); // fix it } } /** This is called when an operator is pressed. */ private void handleOp( int button ) { switch( curState ) { case ARG1_STATE: // store the op and move on op = button; curState = AFTER_OP_STATE; break; case AFTER_OP_STATE: // If the previous button pressed was an op, then replace it. op = button; // replace previous operator, curState = AFTER_OP_STATE; // and stay in same state break; case ARG2_STATE: // If we already have 2 arguments, then // perform the pending operation, and continue // the new one using the result as arg1. doOp(); // do _previous_ op and place result in arg1 op = button; // replace op for _next_ operation curState = AFTER_OP_STATE; break; case AFTER_EQUALS_STATE: // If the previous button pressed was Equals, then // go to AFTER_OP_STATE. // (Note that last result is already in arg1.) op = button; curState = AFTER_OP_STATE; break; default: System.out.println( "Invalid State!" ); reset(); // fix it } } /** This is called when Equals is pressed. */ private void handleEquals() { switch( curState ) { case ARG1_STATE: // If we had been entering arg1, then simply // move to the AFTER_EQUALS_STATE. curState = AFTER_EQUALS_STATE; break; case AFTER_OP_STATE: // do nothing (Equals after Op is illegal) curState = AFTER_OP_STATE; break; case ARG2_STATE: // If we already have 2 arguments, then // do the pending operation and go to AFTER_EQUALS_STATE doOp(); // do _previous_ op and place result in arg1 curState = AFTER_EQUALS_STATE; break; case AFTER_EQUALS_STATE: // do nothing curState = AFTER_EQUALS_STATE; break; default: System.out.println( "Invalid State!" ); reset(); // fix it } } /* * internal convenience methods */ /** This internal method computes: arg1 = arg1 op arg2, * and updates the display. */ private void doOp() { double result = arg2; switch ( op ) { case Calculator.OP_ADD: result = arg1 + arg2; break; case Calculator.OP_SUB: result = arg1 - arg2; break; case Calculator.OP_MUL: result = arg1 * arg2; break; case Calculator.OP_DIV: result = arg1 / arg2; break; } arg1 = result; calc.setText( arg1+"" ); } /** This method converts the text on the display * to a number. It should typically be called after * appendDigitOrDot(). */ private double getNumFromScreen() { double num; String s = calc.getText(); num = cs101.util.Coerce.StringTodouble( s ); return num; } /** Returns true if s has a dot '.' */ private boolean hasDot( String s ) { return ( s.indexOf( '.' ) >= 0 ); } /** Checks if appending a decimal point is legal, and * if so, does it. */ private void appendDot() { String s = calc.getText(); if ( !hasDot( s ) ) { if ( s.equals("") ) { s = "0"; } s += "."; calc.setText( s ); } } /** Takes care of appending a digit or dot to the current display. */ private void appendDigitOrDot( int digit ) { if ( digit == Calculator.DOT ) { appendDot(); } else { calc.setText( calc.getText() + digit ); } } /* * some useful debugging tools */ // state label string constants for debugging private static final String[] STATE_LABELS = { "ARG1", "AFTER_OP", "ARG2", "AFTER_EQUALS" }; // displays the current state on the console private void printCurState() { // Debugging info System.out.println( "Current State: " + STATE_LABELS[curState] + "\n" + "arg1:\t" + arg1 + "\n" + "op:\t" + Calculator.ButtonLabels[op] + "\n" + "arg2:\t" + arg2 ); } // prints the button pressed private void printButton( int button ) { // Debugging info System.out.println( Calculator.ButtonLabels[button] + " pressed. \n" ); } }