Part 2
Understanding the Event Core
In 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.
Where to get the Event Core
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...
addition:(200,100)
or...
addition:(number%,300)
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...
gnu%=addition:(100,200)
...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 Begining
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...
OPEN "C:"+data$,A,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...
sound_vol%=A.sound_vol%
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...
gDEFAULTWIN x,y,width,height
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)...
AfSetStatus%:(AfStatusType%)
AfSetstatusVisible%:(AfStatusOn%)
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.
AfSetCBAVisible%:(AfCVA%)
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...
AfTitleVisible%:(AfTitleOn%)
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.
AfSetTitle:("Title Here",KAfTitleTypeMainTitle%)
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!
The Middle
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...
c$=GETCMD$
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.
ELSEIF LEFT$(c$,1)=KGetCmdLetterBroughtToFGround$
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.
Reading Keypresses
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.
The End
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.
Got any questions or comments arising from this tutorial.
Need help with OPL. Head to the forums
for help from the friendly community.
|