If you remember our first article, I left our example application with a few features which didn't work, or rather didn't do anything. Did you spot what they were? If you ran the example program, you may have discovered that once opened, you could not close the resulting window. The close icon didn't work. Also, you were unable to slide the scrollbars or move the window around the screen. This wouldn't really work for a real application, as people would find the lack of standard window handling rather irritating. However, it does serve to illustrate the fact that every aspect of WIMP programming has to be carefully considered and planned. There are many small things which can catch you out unless you adopt a very methodical approach to your programming.
Don't worry though, as many of these little things will soon come as second nature once you gain the experience of writing a few applications and finding out what can go wrong or cause errors to occur. Although it can sound rather daunting when you're faced with all these new facilities and things to consider, it all gets a lot easier once you've got the basic WIMP framework up and running. In future you can build upon standard procedures for handling the WIMP aspect of your application, whilst you concentrate on adding the actual program features.
Now! Back to working out how to close the window in our demo application, as well as allowing the user to drag the window around the screen and operate the scroll bars correctly. As pointed out in part one, the crucial 'core' of any WIMP application is a small, central REPEAT ... UNTIL loop which polls the WIMP. In my applications, this is right at the top - the following bit of code in our example, to recap.
110 : REPEAT
120 : SYS "Wimp_Poll",1,pb% TO reason%
130 : CASE reason% OF
140 : WHEN 6:PROCmouseclick
150 : WHEN 9:PROCmenu
160 : WHEN 17,18:PROCmessage
170 : ENDCASE
180 : UNTIL end%
I cannot stress the importance of this small loop too greatly. It's the main core of any application and can also determine whether or not you crash or stall the entire desktop. Lets now examine the code in more detail;
The first line starts a virtually infinite loop, until end% is true (line 180). end% only becomes true when we want to quit the application, which we set when someone selects the Quit option on the menu (explained later).
Until then, these lines will loop indefinitely - so what's so special about the code inside this loop?
The crucial line is 120, which issues a system call (SYS) to RISC OS itself. We're calling a routine in the RISC OS ROM called "Wimp_Poll". This is actually internal system call &400C7 - but you don't need to remember that, as RISC OS provides handy 'English' names for all it's internal system calls. This particular one happens to be in the "WindowManager" module.
Anyway, back to our application. What does this mysterious call actually do? Well, as is reasonably self-explanatory (like most RISC OS system calls) it polls the WIMP to see whether or not certain 'events' have occurred.
At this point, I should point out that if you're seriously interested in learning WIMP programming, then an essential reference tome is the Programmers Reference Manual, otherwise known as the PRM. This is quite a hefty addition to your bookshelf, weighing in at four thick volumes. It should be available from RISC OS dealers or at the various RISC OS Shows which take place around the country. You'll refer to this all the time, so it will soon become your Bible. All the WIMP calls are described in volume three, with Wimp_Poll on page 115.
Although I will endeavour to explain everything you need to know in order to understand the various examples I provide, you will require the PRMs in order to check the various parameters and values each call requires, so that you don't end up with unexpected errors such as memory corruption or buffer overflows - all of which sound horribly complicated, but which I hope to explain as we progress through this course.
Right... What does that Wimp_Poll do? and why is it so important to the running of our application and the well-being of our desktop in general?
With all system calls (SYS commands) various 'registers' are passed to them. For the purposes of this course, you can think of these registers as variables and parameters that are passed to the relevant system call in the RISC OS ROM. Some of these registers need to contain certain values, or snippets of information, and many are very important and crucial to the smooth running of your application - and computer in general. A rogue or misplaced register in the wrong place can crash the entire computer, so it's worth understanding how important these registers are, right from the outset. I will explain why these are so important and also illustrate how errors can occur and what happens when they do. More importantly, I will try to explain why your computer will crash if you get something wrong, and how to avoid this situation.
Wimp_Poll requires two registers on entry. It can actually accept three - but I'm trying hard not to confuse you! Reading the PRM will give you the full nitty gritty details of each system call, but for the time being, just trust me. For our immediate programming examples, you can assume that any registers I miss out are unimportant in the grand scheme of our early applications.
Traditionally, RISC OS BASIC can send up to eight distinct registers to each system call, and these are, perhaps boringly, referred to as registers 0 through to 7, or R0 to R7, and this is how they're described in the PRMs. BBC BASIC always sends the registers to system calls in numerical order, so looking back at line 120 in our code, we're effectively calling system call Wimp_Poll with the value 1 in register R0 and the value of pb% (more about this one later!) in register R1.
When RISC OS executes the system call, it does some magic in the ROM and when it's completed the relevant call, it returns its answer back to you in the same registers (R0 to R7) that can be assigned to variables after a TO parameter to the SYS call. Again, they're returned in numeric register order, R0-R7.
In our example, Wimp_Poll does it's magic, then returns to BASIC and our code with its answer in register R0 - to which we're subsequently saying, "Assign TO variable reason%".
Effectively we're telling RISC OS to poll the wimp with two values; 1 and pb%, then return the answer in variable reason%, which we can then act upon.
Now... Hopefully I've not lost you up to this point, because I've got some good news and some bad news.... The good news is that once you grasp the techniques explained here, you'll be 95% to understanding the whole concept of WIMP programming. It's just that it's got a bit of a steep learning curve to get going, but then it levels out and is relatively plain sailing - just repeatedly using the same calls and acting on the answers we get back from RISC OS. Ultimately, this is one of the beauties of RISC OS, in that it handles a lot of the really tricky stuff for us.
However, I did say there was some bad news - which is that it's slightly more complicated by virtue of the fact the variable pb% mentioned above, is actually a 'pointer' to another block of memory which is used for storing additional information. Don't worry though, it's easier than it sounds!
You can visualise pointers in terms of a book case or stack of pigeon holes of the type you used to get in offices when people still gave pieces of paper to each other instead of emails. Imagine you've got many bookshelves. Perhaps you've got a whole library of books? Now, rather than remembering where every individual book is, you may group them into categories, such as all the books by a particular author go on one shelf, or the books on a certain topic or subject go in another shelf. You can then remember that, for example, books on nuclear physics are on shelf three, or that non-fiction books start on shelf seven etc. You've just created two pointers.
All a pointer is, is a short cut to another area of memory, where you can store a set of information relevant to what you require. If you look back in our code (line 70 to be precise) you'll see that pb% is in fact a DIMension array consisting of 256 bytes of memory.
"DIM pb% 256" allocates 256 bytes of consecutive memory and assigns the start of it to pb%. Thus, pb% becomes a pointer which refers to a 256 byte block of memory. We'll use this block a lot in our WIMP application, so I've deliberately called it pb% to refer to 'poll block'. Of course, if you want to make your variables more memorable, there's nothing to stop you calling it something longer, such as wimpblock% or something.
The reason we're sending Wimp_Poll a pointer to a block of memory in register R1 will become clearer as we progress in the course, but essentially it's because various WIMP functions can use it to store values, such as window and icon coordinates etc. It will ultimately help us to organise our application because we know that all the results we require are within those 256 bytes of memory.
Even though not all system calls require 256 bytes to store return data, we allocate RISC OS the full 256 bytes so that it doesn't accidentally run off the end of a shorter block of memory, had we been too frugal on our memory allocation. Thus, imagine if we'd only allocated 50 bytes of memory to pb% but Wimp_Poll (or another call we'll use later) returned 60 bytes of data in return to one of our SYS call requests. Memory blocks are not protected as much as the rest of the system, so RISC OS will simply store the data in a continuous stream, off the end of our memory block - and promptly overwrite whatever happens to be in the memory which follows where pb% was allocated it's bytes. This is where the whole computer can potentially crash or throw up fatal errors. The memory which comes after our allocated block may be very important for something else and not like the results of our application's system call trampling all over it.
This is where the PRMs are essential reading, because they document and clarify exactly how many bytes are required by each system call, so that we can be sure to allocate enough memory for everything. Indeed, if you want to be really safe, there's nothing preventing you from allocating more than you require. For example, we could have used DIM pb% 1000 or something in line 70. This would allocate 1000 bytes to our memory block, so it would be virtually impossible to run off the end of it - certainly with the code I'm going to be using during this course.
However, RISC OS programmers have always been very diligent when it comes to optimising their code, and not needlessly wasting memory. As the PRM explains that Wimp_Poll only requires 256 bytes, there isn't really a compelling reason to use more than that.
Back to our application again... Sorry for a number of temporary diversions, but it's probably best to explain things as we go along, pausing briefly to give a bit of background information or explanation of potential pitfalls, rather than rectifying things later.
Wimp_Poll returns a code (reason%) telling our application precisely what the desktop (or the user) happens to be doing at that exact moment in time. This is why we keep repeating our loop. Wimp_Poll is only a momentary call. In fact, RISC OS can call this many thousand times a second - which is why the RISC OS desktop can feel so responsive to us doing things. Of course, the efficiency of RISC OS is all down to programmers being careful not to hog the WIMP too much, so I cannot stress too much that we want to make our Wimp_Poll loop run as efficiently as possible - and I'll be explaining ways of optimising it and making it even more efficient in future articles.
The only other lines inside our main WIMP polling loop are a CASE ... ENDCASE structure, which tests for different values returned by Wimp_Poll in the variable we've named reason% - which is the event RISC OS has informed us, via Wimp_Poll, that's currently occurring. As you can see in our example, we're only recognising Wimp_Poll event codes of 6, 9 and 17 & 18. You can no doubt work out yourself, there are a number of other codes, which we're not taking any notice of at the moment - which is why our demo application is not allowing you to drag the window around the screen, scroll the bars or close the window.
We're detecting Wimp_Poll event code 6, which is a mouse click. This code gets returned by Wimp_Poll every time the user clicks a mouse button somewhere.
We're also detecting event code 9 which is a menu selection. This is not someone pressing the actual menu button on the mouse - that's still a mouse click, and thus returned as an event code 6. Event code 9 is returned when someone actually clicks SELECT on one of your application's menus. We have to recognise this event code, otherwise we wouldn't be able to detect someone selecting Quit from our menu.
Lastly, we're also recognising event codes 17 and 18. These two codes tend to go together and are what's known as "user messages". They're the most complex of the Wimp_Poll event codes and will be explained as we progress into later tutorials. At present we only have to support them in order to handle a 'Quit' request, in order to quit our application. As I explained at the beginning of the course, we need to cover a lot of information just to get started and write a relatively simple application. At this point we don't really need to have to understand everything, but it will all become clearer as the pieces of our WIMP jigsaw start fitting together and we write more code and gain experience of how it all comes together.
So, how do we fix our niggly little issue of our window not allowing us to drag it or close it?
Try adding the following two lines to our code, which go inside our main Wimp_Poll loop.
131 : WHEN 2:PROCopen(pb%!0)
132 : WHEN 3:PROCclose(pb%!0)
Now save your amended !RunImage file and re-run your application. You'll now discover that our window can be dragged around the screen to our hearts content, as well as being closed by clicking on the 'close' icon.
Despite the necessary complexity of this lesson, we've actually fixed some quite major issues with our application in just two additional lines of code - demonstrating how easy it is, once we overcome the rather steep learning curve involved with WIMP programming.
The two procedures PROCopen and PROCclose were included in the code we entered in lesson one and are very short procedures which open and close a specified window respectively. Which window we open or close depends on the parameter we send to the procedure, and as you can see, it's referring to that memory block pb% again. The ! operator, which is nicknamed the "pling" character is used for referring to elements within the memory block pointed to by pb%. There are actually two operators we can use - the ! pling character and the ? character. The difference is that ? refers to a single byte, and ! refers to four bytes, or a "word". Each operator is followed by a number, which is the actual offset into the memory block - in the two lines above, it's at offset 0, in other words right at the beginning of our 256 block of memory referred to as pb%.
Each of the event codes 2 (open window request) and 3 (close window request) return the code for the window opening or closing (known as the "window handle") in the first four bytes of our memory block. This in turn is sent to our standard procedures for opening or closing the relevant windows.
Although we've only added two lines of code to our application this month, we've actually covered some very important concepts, the understanding of which will make things a lot easier in the future. From lesson three, I promise it will start getting simpler - despite adding more functionality to our demo application.
|
Last edit: 5th Feb 2012 at 9:33pm |
| |||||||||||
|
| ||||||||||||||||||||||||