parent previous next question (Smalltalk Textbook 36)

EngiCalculator

Let's make a calculator to go with the calendar and analog clock of the previous sections. So we'll have a suite of utilities in Smalltalk. The calculator program is in Appendix 21. File it in then evaluate Program 36-1.


Program-36-1: (EngiCalculatorModel; pressButton:, top)
--------------------------------------------------
| calcualtorModel calculationResult |
calcualtorModel := EngiCalculatorModel new.
calcualtorModel pressButton: '3'.
calcualtorModel pressButton: '+'.
calcualtorModel pressButton: '4'.
calcualtorModel pressButton: '='.
calculationResult := calcualtorModel top.
Transcript cr; show: calculationResult printString
---------------------------------------------------

You should see 7 printed on the transcript as a result of 'pressing' several keys ('3' '+' '4' '='). Sending the 'pressButton:' message to the calculator model (an instance of 'EngiCalculatorModel') is equal to pressing the button specified as an argument. The value of the current calculation is available by sending 'top' to the calculator, because the result is stored in the top of the stack.

The 'pressButtons:' message is a convenient way to send several buttons all at once.Program 36-2 achieves the same result as Program 36-1.


Program-36-2: (EngiCalculatorModel; pressButtons:, top)
---------------------------------------------
| calcualtorModel calculationResult |
calcualtorModel := EngiCalculatorModel new.
calcualtorModel pressButtons: #('3' '+' '4' '=').
calculationResult := calcualtorModel top.
Transcript cr; show: calculationResult printString
---------------------------------------------

I arranged the calculator buttons like this.

---------------------------------------------
        'MC'   'M+'   '7'   '8'   '9'   '/'
        'MR'   'M-'   '4'   '5'   '6'   '*'
        'AC'   'r'    '0'   '2'   '3'   '-'
        'C'    '+-'   '1'   '.'   '='   '+'
---------------------------------------------

This program shows you an image of the face of calculator.


Program-36-3: (EngiDisplayModel, EngiCalculatorModel; buttonImage)
---------------------------------------------
EngiDisplayModel openOn: EngiCalculatorModel new buttonImage
---------------------------------------------

The buttons are pretty much self-explanatory, except for 'r', which takes the square root.

Now let's create a usable calculator window.


Program-36-3: (EngiCalculatorModel, EngiCalculatorView)
---------------------------------------------
| calcualtorModel |
calcualtorModel := EngiCalculatorModel new.
EngiCalculatorView openOn: calcualtorModel
---------------------------------------------

Though it is not as high-functionality as a programmable calculator, it is still useful.

Now, let's examine the details of the program. As mentioned before, a sound MVC model should work alone without a view or a controller. This enables our model to work as seen in Program 36-1: you create it, send it a few messages and get some kind of result or utility.

Now it is tempting to start building systems by creating the user interface first, then creating the model. The Smalltalk interactive programming environment encourages this kind of bottom-up hacking. However it is not the best way to do Smalltalk programming. If you make a good model, you can easily create and modify user interface later. But if you start with the user interface, the model often becomes unnecessarily complex because the design is influenced in subtle ways by the interface. Please make it a habit to create the model first and then think of the user interface.

Notice that this does not argue against task analysis or scenario design. The goal of those methods is to elicit the required functionality, not just the desired look of an interface.

The calculator is composed of four classes. The inheritance trees are:

---------------------------------------------
Object ()
. Model ('dependents')
. . EngiCalculatorModel ('stack' 'stream' 'operator' 'memory')
---------------------------------------------
Object ()
. VisualComponent ()
. . VisualPart ('container')
. . . DependentPart ('model')
. . . . View ('controller')
. . . . . EngiCalculatorView ()
---------------------------------------------
Object ()
. VisualComponent ()
. . VisualPart ('container')
. . . DependentPart ('model')
. . . . View ('controller')
. . . . . EngiCalculatorDisplay ()
---------------------------------------------
Object ()
. Controller ('model' 'view' 'sensor')
. . ControllerWithMenu ('menuHolder' 'performer')
. . . EngiCalculatorController ()
---------------------------------------------

Only the model has instance variables. That means the user interface parts (the view and the controller) don't do much. The 'stack' instance variable of the model is an evaluation stack for calculation. 'stream' contains a string to push onto the 'stack'. 'operator' keeps operational symbols (#add, #sub, #mlt, #div) which corresponds to buttons ('+', '-', '*', '/'). 'memory' is used as temporary memory for calculations.

The model has three class variables 'ButtonImage', 'ButtonMap', 'ButtonTable'. 'ButtonImage' keeps the visual image of buttons as a PixMap. 'ButtonMap' translates the mouse coordinates in the visual display and the "button coordinates" of the calculator model.

For example '(4@4 corner: 34 @ 26) -> (1 @ 1)' means that any mouse clicks in the rectangle located at 4@4 and extending to 34 @ 26 will be considered to be clicking on button '(1 @ 1)', or the top-left button. The '(1 @ 1)' button is the 'MC' (Memory Clear) button.

'ButtonTable' is an association of location, internal code, external code of buttons. An example is "(6 @ 4) -> #add -> '+'". This means the button at position '(6 @ 4)' will be displayed in the view as '+' and will affect the model by adding the #add message when clicked.

So when the controller detects a mouse click, it uses (whichButton:) to find which button has been pressed.

'EngiCalculatorDisplay' updates the display of the calculator when it is notified that the value of the calculator (top of the evaluation stack) has changed. The button area ('EngiCalculatorView') does not have to do anything. So the 'update:' message is defined to update the display in 'EngiCalculatorDisplay' but to do nothing in 'EngiCalculatorView'.

This calculator accepts only mouse input. How would you change it to accept keyboard input also?


parent previous next question
Copyright (C) 1994-1996 by Atsushi Aoki
Translated by Kaoru Rin Hayashi & Brent N. Reeves