Building a simple application
To pick up where Part 1 of this tutorial left off, we going to build a simple application, one that is missing from the BeOS distribution : a simple, four operations only, calculator. Of course, we could go over to BeBits and download a better one, but it is much more fun to make our own calculator, no?
Our implementation in Squirrel of a very simple calculator, we will be calling "EzCalc" and will look, when execited, like this :
Let's start by creating the window :
Font.init
|
| This command gets all the fonts available on the system. It will allow us to create a font object and use it to specify the font of the widgets. |
make "font Font 'Comix Sans MS' $font~size "set 10
|
| We creating a font object to be stored in the variable font from the font installed on our system Comix Sans MS. We also setting the size of this font to size 10. |
make "win Window "titled 'EzCalc' [100 100] "not.zoomable "not.resizable
$win~config "font "set :font
|
| We have now created the window (to be stored in the variable win) with the title EzCalc. The two last inputs of the command define the behavior of the window. In this case we don't want the user to be able to resize the window or to zoom in on it. The list [100 100] defines the coordinates where the window will appear. In Squirrel, all widgets inherit of the color and font of their parent. We want the widgets placed in the window to all have the same font styles and sizes, so we set the font of the window, the one created earlier. Unless the font is changed for a specific widget, this default font will be used. |
$win~show
|
A window is not automatically visible after it creation. You need to call the method show on it to make it visible. It's better to show a window last, when all of the initialisation and gluing has completed, for better performance. |
In Squirrel, a window adapts its size to fit its content. Here we don't have any widget glued on the window, the size of the window is just fixed for the window to be visible.
Now that we have our window, let's define what is happening when the user wants to quit the application : The user will have to confirm that he wants to quit.
We keep the same code, and we add just after the setting of the window font and before the show method the following code :
to ConfirmQuit :src
output not (Question "info ["Yes "No] 'Do you really want to quit ?')
end
Hook :win "quit "ConfirmQuit
|
When the close button of the yellow tab of a window is used, the quit method is called, the event quit is sent to the window. If we setup a hook for this event like we are doing here, the function ConfirmQuit will be executed. This function return a boolean negation of the output of the command Question. Indeed as this command Question returns the index of the button clicked by the user in the list ["Yes "No], if the user clicks on Yes it will return 0, and thus true will be returned by our function. Squirrel is waiting the value true to be returned by the function when we really want to quit and false otherwise. |
A simple calculator like the one we intend to build has two major components : A LCD screen to display the number and a keypad. We going in to use this simple design by creating two frames in which we will put all the widgets needed :
make "Lcd Frame "lowered
|
| We are creating the frame that will receive the widget playing the role of a LCD screen. To add a nicer effect, we specify that this frame must have a lowered look. |
make "Pad Frame
Glue :win "top [] :Lcd :Pad
|
| Next we create the frame which will contain the buttons and we glue the two frames on the window. |
Squirrel DR4 does not have at its disposal a LCD widget, so we going to use a simple Text widget to simulate this part of the calculator :
make "Display Text '0' "right
|
| The variable Display will store the widget used to display the number. As we want the text to be justified right we add the word "right as last input of the command. |
$Display~config "bgcolor "set :White
|
| As we know, a widget inherits the properties such as color and font from its parent, we need to configure our text widget with a different background color in order to give it a white background to our LCD screen.
|
$Display~config "expand.x "set true
$Lcd~config "expand.x "set true
|
Instead of fixing a size to our LCD widget, we want it to adapt its size to whatever will be the window's size. The LCD screen should be the same width as the window, so we are using the config method to set the expand property on the width to true. Also, we do the same thing for the frame Lcd which holds the text widget, or else it will not work as we want it to. |
Glue :Lcd "top [] :Display
Glue :win "top [] :Lcd :Pad
|
| We now attach the text widget to the frame, and the two frames on the window. |
As an example, you can observe how the window size has been adapted to the size of its content.
The next step is to add all the buttons composing the keypad of the calculator. For that we going to make a difference between the numbers, the operator keys and the commands like 'AC' and 'CE/E'. Each of these buttons are pretty mutch the same except the action that they perform, so the better way to create them is to use a function for each type of button we want :
to Num :label :val
make.local "button Button :label
Hook :button "invoked "NInvoked :val
output :button
end
|
| The function Num will create a number button. It creates a button first (stored in a local variable) and then sets up a hook for when the user clicks on it (event invoked) and return the button. This function requires two inputs which are the label of the button to be displayed and the value of it. For example if we creating the button for the number 7, we will call this function by doing : Num '7' 7 . |
to Operator :label :code
make.local "button Button :label [1 0]
Hook :button "invoked "OInvoked :code
output :button
end
|
| The function Operator will create an operator button. Instead of giving this function a value for the button, we give it a code. We will use this number later for processing the operation. |
to Command :label :code
make.local "button Button :label [1 0]
Hook :button "invoked "CInvoked :code
output :button
end
|
| As well, the function Operator is the same and will create an operator button. Instead of giving this function a value for the button, we give it a code. We will use this number later during the computation. |
The keypad of a calculator can be divided into rows or columns. We going to use the row separation for EzCalc and create four rows of buttons. Each row will have four buttons except for the first row containing the command button.
make "row0 Frame
make "ac Command 'AC' 0
make "ce Command 'CE/C' 3
Glue :row0 "left [] :ac :ce
|
| As we use our function, creating each row is rather simple and short. We first create the frame, here row0, the buttons are to be placed on it like ac and ce, and we glue them onto the frame. |
make "row1 Frame
make "b7 Num '7' 7
make "b8 Num '8' 8
make "b9 Num '9' 9
make "div Operator '/' 0
Glue :row1 "left [] :b7 :b8 :b9 :div
|
| The frame, row1, will contain the numbers 7, 8, and 9, and the division operator (Take note of the fact that we will use the number 0 later, as specified in the Operator command, to recognize the operator). |
make "row2 Frame
make "b4 Num '4' 4
make "b5 Num '5' 5
make "b6 Num '6' 6
make "mult Operator '*' 1
Glue :row2 "left [] :b4 :b5 :b6 :mult
|
| The frame, row2, will contain the numbers 4, 5, and 6, and the multiplication operator (to be known later as operator 1). |
make "row3 Frame
make "b1 Num '1' 1
make "b2 Num '2' 2
make "b3 Num '3' 3
make "minus Operator '-' 2
Glue :row3 "left [] :b1 :b2 :b3 :minus
|
| The frame, row3, will contain the numbers 1, 2, and 3, and the substraction operator (to be known later as operator 2). |
make "row4 Frame
make "b0 Num '0' 0
make "dot Command '.' 1
make "eq Command '=' 2
make "plus Operator '+' 3
Glue :row4 "left [] :b0 :dot :eq :plus
|
| The frame, row3, will contain the number 0 and the addition operator (to be known later as operator 2). We also add the command for equals (=) and the dot, which will be used when entering float (decimal) number. |
Glue :Pad "top [] :row0 :row1 :row2 :row3 :row4
|
| We add the 5 rows created on the pad frame |
Now that our GUI is complete for this first version of EzCalc, we are going now to simulate the calculator.
First of all, we need to setup how the numbers are entered by the user. We have in the function Num used for the hook to the event invoked the word "NInvoked which describes the name of the function called each time the user clicks on a button. This function will be called by any of the number buttons with each time the value of the button (1, 2, 3, 4, 5, 6, 7, 8, 9, or 0). We define the function Compose to perform the operation of building the number :
to Compose :val
if :Point {
incr "Prec
make "Decimal :Decimal + (:val / :Pos)
make "Pos :Pos * 10
if :Prec>:PrecMax {
make "PrecMax :Prec
}
} {
make "Integer (:Integer * 10) + :val
}
make "Nombre :Integer + :Decimal
Affiche :Nombre
end
|
We going to use 7 global variables to do the job :
- Point , to specify when the user is entering a float (when he has invoked the dot button)
- Prec , to keep track of the precision of the float entered by the user
- Decimal , to contain the decimal part of the number entered by the user
- Pos , to keep track when entering the decimal part of a number
- PrecMax , to keep track of the maximum precision of the float entered by the user during an operation
- Integer , to contain the integer part of the number entered by the user
- Nombre , to contain the current number entered by the user
We, at the end of this function, call the function Affiche to display the number. This function is:
|
to Affiche :val
if (is.float :val) {
if :PrecMax < 1 {
Precision 3
$Display~text "set string :val
} {
Precision :PrecMax
$Display~text "set string :val
}
Precision :Prec
} {
$Display~text "set string :val
}
end
|
We use the command Precision to switch the precision showed when transforming the float stored in the variable val in a string. This way, if the user has entered an number with 4 numbers after the dot, 4 and only 4 will be shown. We now have to define the NInvoked hook to have all the number button call the Compose button : |
to NInvoked :src :val
Compose :val
end
|
| We also need to create the global variables used in the function Compose : |
make "PrecMax 0
make "Prec 0
make "Nombre 0
make "Integer 0
make "Decimal 0
make "Point 0
make "Pos 10
|
When the user clicks on the buttons 1, 2, and 3 of the calculator keypad, we get:
To handle the float number, we need to enable the dot button first. We have saw earlier than this button will call when invoked the function CInvoked with the input 1. As well, this function will process the other command button like 'AC', 'CE/E' and '='.
Before going furter, it's time to see how the operation, are going to be handled by our calculator. When using a simple calculator, the user enter first a number, then hit the operator he want and then enters another number. To compute the result of the operation, he could either hit the '=' button or an operator.
We need to add a global variable which will store the number entered before the operator : Previous, we also need a global variable to store which operator has been selected : Opcode. And now we can define the CInvoked function :
to CInvoked :src :code
if :code=0 {
; AC
make "PrecMax 0
make "Prec 0
make "Previous 0
make "Nombre 0
make "Integer 0
make "Decimal 0
make "Opcode -1
make "Point 0
make "Pos 10
Affiche 0
|
| When the user clicks on AC the calculator must be re-initialized. Here, we just set all the global variables to there original values and display 0 on the LCD screen. |
} {
if :code=1 {
; .
make "Point 1
|
| Hiting the dot button will allow the user to enter the decimal part of a float number, we just switch the variable Point to 1. |
}{
if :code=2 {
; =
make "Previous Compute
make "Nombre :Previous
Affiche :Previous
make "Opcode -1
|
| When the user hits the = button, we need to compute the value of the operation and display it. We also need to keep this result for a possible further computation, and we switch the Opcode variable to -1 as the operator entered by the user before it has been used. |
} {
if :code=3 {
; CE/C
make "Nombre 0
make "Integer 0
make "Decimal 0
make "Point 0
make "Pos 10
Affiche :Previous
|
| When the user hits the CE/C button, we need to discard the last number entered but not the last operator or the first number entered if any. We also display back on the LCD the last number entered. |
}
}
}
}
end
|
To complete our calculator, we need to add the function OInvoked, which is executed when the user hits an operator button. When this event occurs, we need to compute the value of the previous operation, if possible (for example if the user does : 3 + 4 +), and also keep track of the operator selected :
to OInvoked :src :code
make "Previous Compute
Affiche :Previous
make "Opcode :code
end
|
| We store the result in the global variable Previous, display the new result and store the operator code in the variable Opcode. |
We now define the function Compute, which, according to the 2 numbers entered and the operator selected, computes the result :
to Compute
if :Opcode <> -1 {
|
| As this function is called from OInvoked, we need to check that an operator has been entered, or else there's nothing to compute. |
make.local "val 0
if :Opcode = 0 {
; divide
if :Nombre <> 0 {
make "val :Previous / :Nombre
}
} {
if :Opcode = 1 {
; multiply
make "val :Previous * :Nombre
} {
if :Opcode = 2 {
; minus
make "val :Previous - :Nombre
} {
if :Opcode = 3 {
; plus
make "val :Previous + :Nombre
}
}
}
make "Prec 0
make "Nombre 0
make "Integer 0
make "Decimal 0
make "Point 0
make "Pos 10
output :val
|
| The result is computed in the local variable val, which is returned by the function. The calculator is also set to accept a new number. |
} {
make.local "val :Nombre
make "Prec 0
make "Nombre 0
make "Integer 0
make "Decimal 0
make "Point 0
make "Pos 10
output :val
|
| If no operator has been selected, we are in the case where the user is entering the operator after the first operand, so we return the number and get ready to accept the second operand. |
}
end
|
Our application is now almost complete. We may want now to enable the keyboard input to be more user friendly. When the user hit a key of the keyboard, the event keydown is sent to the widget that has the focus. We going to set the focus on the frame Pad and then in the hook function process the key :
to Keyboard :src :k :c
|
| The hook function must have three arguments, all of which will be filled by Squirrel. k is the character of the key and c a code (integer) representing the key pressed by the user. |
if (:c>=48) and (:c<=57) {
; a number
Compose :c-48
} {
if :c=47 {
; divide
OInvoked 0 0
} {
if :c=42 {
; multiply
OInvoked 0 1
} {
if :c=45 {
; substract
OInvoked 0 2
} {
if :c=43 {
; add
OInvoked 0 3
} {
if :c=97 {
; AC
CInvoked 0 0
} {
if :c=46 {
; .
CInvoked 0 1
}
if (:c=61) or (:c=10) {
; equal or enter
CInvoked 0 2
} {
if :c=99 {
; CE/C (key c)
CInvoked 0 3
}
}
}
}
}
}
}
}
}
end<
|
We now set the focus to the pad by adding the next line just before the show method of the window:
We now have a fully functional, basic calculator for your computer.
Squirrel DR4, which has been used for this tutorial, should be available from http://www.electechno.com within a couple of weeks. BeGroovy will keep you posted on its status.
All questions and comments are welcomed. Please send them to.
The complete source for EzCalc follows:
Precision 0
load '/boot/apps/Squirrel/Libraries/Colors.sqi'
/* We init the font, and chose one to be used */
Font.init
make "font Font 'Comix Sans MS'
$font~size "set 10
make "win Window "titled 'EzCalc' [100 100] "not.zoomable "not.resizable
$win~config "font "set :font
; Ask the user if he confirm the Quit
to ConfirmQuit :src
output not (Question "info ["Yes "No] 'Do you really want to quit ?')
end
Hook :win "quit "ConfirmQuit
; window content
make "Lcd Frame "lowered
make "Pad Frame
; LCD frame
make "Display Text '0' "right
$Display~config "bgcolor "set :White
$Display~config "expand.x "set true
$Lcd~config "expand.x "set true
Glue :Lcd "top [] :Display
; Pad frame
; function creating the buttons
to Num :label :val
make.local "button Button :label [1 0]
Hook :button "invoked "NInvoked :val
output :button
end
to Operator :label :code
make.local "button Button :label [1 0]
Hook :button "invoked "OInvoked :code
output :button
end
to Command :label :code
make.local "button Button :label [1 0]
Hook :button "invoked "CInvoked :code
output :button
end
; creation of the button
make "row0 Frame
make "ac Command 'AC' 0
make "ce Command 'CE/C' 3
Glue :row0 "left [] :ac :ce
$row0~config "expand.x "set true
make "row1 Frame
make "b7 Num '7' 7
make "b8 Num '8' 8
make "b9 Num '9' 9
make "div Operator '/' 0
Glue :row1 "left [] :b7 :b8 :b9 :div
make "row2 Frame
make "b4 Num '4' 4
make "b5 Num '5' 5
make "b6 Num '6' 6
make "mult Operator '*' 1
Glue :row2 "left [] :b4 :b5 :b6 :mult
make "row3 Frame
make "b1 Num '1' 1
make "b2 Num '2' 2
make "b3 Num '3' 3
make "minus Operator '-' 2
Glue :row3 "left [] :b1 :b2 :b3 :minus
make "row4 Frame
make "b0 Num '0' 0
make "dot Command '.' 1
make "eq Command '=' 2
make "plus Operator '+' 3
Glue :row4 "left [] :b0 :dot :eq :plus
; we glue the whole
Glue :Pad "top [] :row0 :row1 :row2 :row3 :row4
; we glue the frame on the window
Glue :win "top [] :Lcd :Pad
; we define the function now
to Compose :val
if :Point {
incr "Prec
make "Decimal :Decimal + (:val / :Pos)
make "Pos :Pos * 10
if :Prec>:PrecMax {
make "PrecMax :Prec
}
} {
make "Integer (:Integer * 10) + :val
}
make "Nombre :Integer + :Decimal
Affiche :Nombre
end
to Affiche :val
if (is.float :val) {
if :PrecMax < 1 {
Precision 3
$Display~text "set string :val
} {
Precision :PrecMax
$Display~text "set string :val
}
Precision :Prec
} {
$Display~text "set string :val
}
end
to NInvoked :src :val
Compose :val
end
to CInvoked :src :code
if :code=0 {
; AC
make "PrecMax 0
make "Prec 0
make "Previous 0
make "Nombre 0
make "Integer 0
make "Decimal 0
make "Opcode -1
make "Point 0
make "Pos 10
Affiche 0
} {
if :code=1 {
; .
make "Point 1
} {
if :code=2 {
; =
make "Previous Compute
make "Nombre :Previous
Affiche :Previou
make "Opcode -1
} {
if :code=3 {
; CE/C
make "Nombre 0
make "Integer 0
make "Decimal 0
make "Point 0
make "Pos 10
Affiche :Previous
}
}
}
}
end
to OInvoked :src :code
make "Previous Compute
Affiche :Previous
make "Opcode :code
end
to Compute
if :Opcode <> -1 {
make.local "val 0
if :Opcode = 0 {
; divide
if :Nombre <> 0 {
make "val :Previous / :Nombre
}
} {
if :Opcode = 1 {
; multiply
make "val :Previous * :Nombre
} {
if :Opcode = 2 {
; minus
make "val :Previous - :Nombre
} {
if :Opcode = 3 {
; plus
make "val :Previous + :Nombre
}
}
}
}
make "Prec 0
make "Nombre 0
make "Integer 0
make "Decimal 0
make "Point 0
make "Pos 10
output :val
} {
make.local "val :Nombre
make "Prec 0
make "Nombre 0
make "Integer 0
make "Decimal 0
make "Point 0
make "Pos 10
output :val
}
end
to Keyboard :src :k :c
if (:c>=48) and (:c<=57) {
; a number
Compose :c-48
} {
if :c=47 {
; divide
OInvoked 0 0
} {
if :c=42 {
; multiply
OInvoked 0 1
} {
if :c=45 {
; substract
OInvoked 0 2
} {
if :c=43 {
; add
OInvoked 0 3
} {
if :c=97 {
; AC
CInvoked 0 0
} {
if :c=46 {
; .
CInvoked 0 1
} {
if (:c=61) or (:c=10) {
; equal or enter
CInvoked 0 2
} {
if :c=99 {
; CE/C (key c)
CInvoked 0 3
}
}
}
}
}
}
}
}
}
end
; we intercept the keybord event
Hook :Pad "keydown "Keyboard
; we define global variable used
make "PrecMax 0
make "Prec 0
make "Opcode -1
make "Previous 0
make "Nombre 0
make "Integer 0
make "Decimal 0
make "Point 0
make "Pos 10
; we set the focus to the Pad frame
Focus :Pad
; and we show the window
$win~show
|
|