squirrel32x32

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 :

ezcalc1

 

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.

ezcalc2

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.

ezcalc3

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.

ezcalc4

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

ezcalc5

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:

ezcalc6

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:

Focus :Pad

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