Part 2: Understanding the Event CoreIn which we'll introduce FreEPOC's event core, talk through the workings of this OPL framework of code, learn more about Procedures (and how to pass numbers to them), examine key inputs and the menu system, and finish up by seeing a program exiting safely.
The Event Core we're using is provided by FreEPOC. It can be downloaded from their website (www.freepoc.org/core.htm). You should download this so you can look through the OPL code as we discuss it here. Intallation instructions (for OPL) are in this tutorial.
Building Blocks: Procedures (Again)
Right then, in the last lesson, you got to grips with procedures. Before we move on to look at the Event Core, I'm going to show you how to make Procedure a bit more efficient. Have a look at this.
PROC addition:(one%,two%) LOCAL foo% foo%=one%+two% RETURN foo% ENDP
Now this is a silly simple procedure that doesn't do much that couldn't be done in a single line of code, but it shows you two more principles. And you should be able to see how more complicated work can be done inside the procedure.
Passing Variables to Procedures: Local Constants.
Where we've named the procedure we've added two integers inside some brackets. When you call the Procedure you need to supply these two numbers like this...
one% and two% don't need to defined as variables, because they aren't variables. They're constants that only exisit inside a single procedure. Think of them as the LOCAL variant of a constant. After you leave the procedure, their value is lost - and this is why you need to pass a number to the procedure every time you call it (although obviously it can be different every time!)
Returning Variables from Procedures
Here's something handy. If instead of calling our addition subroutine by a simple line, if we call it like this...
...we can make gnu% equal the number that we RETURN from the addition procedure. When you work through a procedure and come across the RETURN statement, the program does not read the rest of the lines in the procedure, but returns immediatly, as if the ENDP line had been ready.
If you don't give a RETURN and ENDP is reached, then ENDP will return the number 0.
How The Program Story Unfolds
In part one I asked you to think of OPL as a language. I want to continue this analogy and ask you to think of your program as a story. Like any good fictional construction, you need three parts to any story. The beginning, the middle and the end.
The begining of your OPL Program will gather information about the computer and about the personal preferences that the user is using in the program. It will then go on to set up any graphics the program needs. Finally, it will draw the main screen, and have everything ready for the middle.
The middle of the program is where all the fun happens. It's based round running a simple looping routine which checks for keypress and messages from the computer, processng these keypresses, checking the results, and going around the loop again until the program has to stop
The end of the program cleans up things that shouldn't be left behind, saves any changes to the preferences, saves any data that may need saved, and exits the program.
So, you'll get something like this in the main procedure...
PROC main: GLOBAL exit% initialise: exit%=0 DO main_loop: UNTIL exit%=1 clean_up: ENDP
Notice how we use a variable that can be set anywhere within the main_loop: to exit the program. This is so that the computer can always finish what it needs to do, come back to the main procedure, and then move to the clean_up: procedure to make sure everything is saved.
Let's look at what happens in each section in more detail. It might be an idea to open the event core OPL code (core.opl) so you can follow the code through, we'll only brush on the highlights here.
The majority of the work is carried in the Event Core's initialise: routine in the following order.
Where Are All The Files
First we check to see if we can locate the 'support' files for the running app. As almost every app you'll program will have some graphics, this section is dedicated to looking for the graphics .mbm file. Using the name of the applicaction specified in one of the constants (look at the very top of the code for these), we look through first the C: (internal disk) and then D: (the MMC card). If we find it, then we save the location in both path$ and data$. Path$ includes the drive the .mbm file is on, and data$ stores it without the drive information (you'll see why in a second).
You'll notice that this check uses the IF... ELSE... ENDIF... loop we discussed in part one, but we add in an extra IF check using ELSEIF. In this way you can have as many decisions as needed in an IF statement.
Personal Preferences - .ini Files
Now we load in the users personal preferences. These are stored in core.ini (or whatever the application's name is). We goto PROC LoadINIFile:. If the file exists, we open the file. If it doesn't exist, we enter some default values, and then immediatly save them.
This is where we use data$, as .ini files should always be stored on the internal disk (hence "C:"+data$). In the core, we only have one preference, which is the sound volume. The .ini file is in fact a small database. Each of these databases is allocated a letter when opened, and then each field is given a temporary label...
So we're opening our applications .ini file on the c drive, we'll refer to it as database "A" and the only field is going to be temporarily refered to as sound_vol%.
We can now set the application variable to that in the database with the line...
Note how we use the database letter and the label. It's good practice to give the labels the same name as the variable in the application, but it is not a requirement.
And when we're finished, we CLOSE the database. You should always close things when you're finished with them. It means you use less memory, and your application will run faster.
Saving the preferences is a very similar process. Firstly we ake sure the directory exisits on the C drive. Once we're sure this is made, we DELETE the old (un-needed) .ini file, CREATE a new one and set the values.
Once all the values are set, we must save the changes into the database by using the APPEND function. This adds the information to the end of the database. As it is a new database (we deleted the old one) then it will always be the first record. We then CLOSE in the same way as before.
You'll notice the use of TRAP as a keyword. TRAP simply suppresses any error messages that may occur from the command on the rest of the line. This way, if the directory we are trying to create already exists, our program won't panic and stop, but will carry on.
Setting Screen Sizes and Toolbars
Whenever an OPL program is started, it will immediatly create for itself a blank window taking up the full size of the screen. Now, some programmers will use this window, others will leave it sitting in the background, and some will change its size so it hides anything behind it, but isn't actually used for anything. This is the approach our Event Core uses...
is where we alter the default window. (x,y) determines where the top left corner of the default window is (in relation to the top left of the screen), and width and height change the dimensions of the window.
The width of the whole screen is 640 pixels. Looking at the left hand toolbar, this can either be off (0) or on (1). The size is set by the type, small (1, which is 32 pixels wide) or standard (0, which is 96 pixels wide)...
The right hand CBA button can be either always visible (1), or only show up when a CBA button is pressed (0). The width is between 80 and 130 pixels, depending on how long the text labels for the buttons are.
The title bar is the blue title that stretches along the top. It is XX pixels high. Set is as being visible (1) or hidden (0) with...
You can also specify what text is shown in the title bar with the folowing command, Note that you would normally have the program's name in the title bar. Note the Constant used from the standard external file to say it is the main title.
All the above values for these bars are held by Event Core in the .ini file. At the moment, you decide what bars are shown by your app when you code it, and they do not change. Later on, you may want the user to be able to decide what they want to see or not see, so you will need those values stored in the .ini. This is a good example of thinking ahead when programming, even if (in the end) it is something you never use.
Now, to start our display, we create one window that fits within the the tree bars (status, title, cba) if they are open or not. This is the primary screen and we'll do most of our work in here. Just to prove that it is working, we also copy the FreEPOC logo into the window from an external graphics file. We're taking a detailed look at graphics commands in the next part, so for the meantime I'll leave these unexplained. See if you can work out what's going on!
Rather than the simple loop shown at the start of this part, our Event Core has two DO... UNTIL loops, one within the other (this is called a nested loop). This is to help with applications that are on a 'level by level' design (eg Vexed). What happens is the 'outside loop' reads something like this...
exit%=0 DO load_the_next_level: initialise_level: level_completed%=0 DO play_the_game: UNTIL level_completed%=1 UNTIL exit%=1
From this, you'll see that this makes a lot more sense, is easier to read, and keeps your app as small and easy to follow (and program) as possible.
The Main Event Loop
This is scary stuff, and there is no simple way to ease you into it. The best you can do is understand what is going on, and leave it at that.
Commands From The System
Firstly, the program checks to see if it has been told to do anything by the computer. The computer sends messages in a text string, and you retrieve this with...
Three options concern you...
IF LEFT$(c$,1)=KGetCmdLetterExit$ Exit:
This is self explanatory, and tells the app that it is to close immediatly. We could use the OPL command STOP here which closes everything, but we want to save our preferencesand exit gracefully. So we jump to our exit: procedure (which we talk about in a few paragraphs.
ELSEIF LEFT$(c$,1)=KGetCmdLetterBackup$ Exit:
Pretty much the same as before. here the Communicator is being backed up. Again we want to gracefully exit, saving changes as we go, so the most recent version of the information has been saved.
The program has been jumped to. Now in some programs, you'll need to update screens and variables, in others you won't need to alter anyting due to being in the background for some time. It depends on the application. If you needed to have a procedure when called back, you'd jump to it from here.
Next the system decides if it has been sent a keypress to act on. If it has, it jumps to a procedure (PROC g_kbddrv: for keyboard driver) to process this key.
(If ER6 OPL is ever installed on devices such as the P800 or 7860, then a further check would be made here for pen taps. If this ever happens, I'll gladly write another tutorial on pen events!)
Acting on CBA Buttons
If a CBA button is pressed, then the application jumps to the XXXXX-CBA routines and decides what button was pressed (1, 2, 3, or 4), and jumps to the relative procedure. This is a call-back function and because of this, you cannot exit an application from within a call-back function. This is a problem if you press CLOSE. So rather than jump to the exit, we simply set the level_completed% flag (or whatever variable we are using to scan for "I want to exit now") and let the main loop routine exit that application gracefully.
Processing the Keypress (g_kbddrv:)
You'll spend a lot of time in this procedure. Here is the meat of your app. If a user presses a key, it's this bit of code (with a very long IF statement) that will call the correct procedures and carry out the relevant actions.
The first few sections of this procedure concern themselves with the modifier keys, such as Shift, Ctrl, Fn, and their various combinations.
The next line detects the menu key. If the menu key is pressed, then we jump to the g_menu: procedure, which will RETURN the equivalent keypress. (ie if we select Save-ctrl-s from a menu, then the procedure returns the letter s).
Now we look for two special events, the Infrared being switched on or off, or a request to add a shortcut to the application onto the desktop. You need to add these in the Menu subsystem (that's coming up!). Now, you'll see that the two procedures called are not in core.opl. Where are they?
The answer is hiding at the very top of the code in the lines...
INCLUDE "AppFrame.oxh" INCLUDE "System.oxh" INCLUDE "DBase.oxh" INCLUDE "SendAs.oxh"
These OPL Extension Header files refer to things called OPX's. OPX's are small items of C++ machine code that can be called from OPL by a procedure call. OPX's usually deal with calls directly to the CPU to do specialist jobs - in this case the infrared and Desk screen. Any OPX's INCLUDED can be called at any time in the same way as normal.
We'll look at OPX's in a later tutorial.
And now we're at the part that actually does something...
ELSEIF Key&=%A g_About:
...and so on. This part of the statement checks if shift-a has been pressed. Two things to note. The first is the % in front of the letter. This is a bit of OPL shorthand that says (the computer code for the following letter). 'A' is represneted by the number 97, so %A=97. This makes your code easier to read, and still understandable to the computer.
The second is that %a is different to %A. The second is shifted, the first isn't.
The Menu Procedure - Showing What You Can Do
The menu procedure is a piece of cake. First you initialise the menu system with the command mINIT. You then define each top level menu (eg File... Edit...) in an mCARD command like this...
mCARD "File","Create new file...",%n,"Open File",%o
The first string is "File" and this is what appears in the top menu bar. The next pair of string and number is the first line, with the text of the option ("Create New File...") as the first, and the hotkey that this command represents the second.
You'll notice that these numbers (%n for New file) are the same numbers that we look for in the g_kdbdrv:. So you program once and get two outputs (hotkey and menu). This is what all the modifications are for.
We then use a temporary variable to store the result of the MENU command (which displays the menu and returns the simulated hot key).
Having a second menu come off one menu option (eg any menu with "More >") is something you may want to use. Fistly define the second menu (the one that is sprouting from the first) using mCASC. Then using a ">" symbol, refer to it in any mCARD line after the mCASC.
mCASC "Databases","Create",%c,"Query...",%u mCARD "File","Databases>",16
The number 16 puts up the little arrow symbol where the hotkey definition would normally appear.
At some point our application will need to stop. Those looking through an OPL command list will find the useful STOP command. But you need to do a few things first. This is why when our Event Core needs to stop, it calls PROC exit:
This routine does a few vital housekeeping tasks. Firstly it saves the current preferences. It then closes all the graphical windows (so the computer can reclaim the memory efficiently).
Only then can we safely STOP the application.
The First Preview!
Phew! There's a lot going on there. Hopefully you've been able to read through the code at the same time as this part. It does make it easier to understand, trust me.
Now you know what's going on, it's time to press the compile button. After a few moments, you'll be asked if you want to run the compiled program (if you get an error message, check carefully what you've typed and make sure everything is correct. even a stray comma can upset the translation). After a few moments you'll be presented with the FreEPOC logo on the screen, and the menu and Command Buttons at the side should response. Have a play around and see what happens.
You've just compiled the OPL code of the Event Core! Don't be afraid to change the code and experiment. You can't damage your Communicator doing this, so don't worry.
Published by Ewan Spence at 14:35 UTC, August 25th