Copyright (c) 2012-2016 Hyperion Entertainment and contributors.
AmiWest 2013 Lesson 3
At AmiWest 2012 we took a look at a simple application which presented a graphical tree of all the Processes running on your Amiga. I don't believe we spent enough time exploring this example which is what this lesson will focus on.
Full source code is available from here.
The ProcTree example uses many Amiga-only features to setup the application including:
- Embedded version string
- Stack cookie
- stdio window control
- Startup from Shell and Workbench
- C library provided interfaces
For an application you generally want to keep main() simple and to the point. This enables easier cleanup when errors occur because you only have a limited number of function calls to clean up after during a failure.
This function handles the GUI overall. It will open the various BOOPSI classes and set up the global pointers. It must always clean up whatever resources are allocated as well.
The purpose of this function is to handle the GUI window itself. Before it can do that, it needs to gather some information to display in the window. It gathers all the data required into one data structure (struct GuiData) to make things easier.
We are finally down to the function which does the real job of opening the window. This is a function because, in general, there will be many options you will want to consider for your GUI. That usually means a plethora of tags and a lot of autodoc reading to figure out what they all do.
One tag in particular we will be studying is LAYOUT_DeferLayout. More about this critical option will be covered later.
This is the real meat of your GUI. Pretty much every GUI toolkit on the planet operates with some form of event handling scheme. An event is something of interest to your application. It could be a mouse button press. It could be a key press. It could be a pressure sensor from a tablet. You may also get refresh events from the OS which tells you when to redraw parts of your GUI.
The secret to the entire thing is this line:
uint32 sigmask = IExec->Wait(wait_mask);
Without that Wait(), multitasking would grind to a halt.
After your program is done waiting for something to happen, it needs to act on whatever events show up. Events can show up in any order at any time. Such is life in a multitasking system. So you need to code defensively and be able to handle any situation.
Here is where things get a bit murky. When you are working with a basic Intuition Window, you need to handle a lot more details than you will have to handle here. See Intuition Windows for all the details.
The reason you don't have to do most of the work is because the window.class object is taking over some of the responsibility. It tries to do all the basic things you need so that your application is not burdened with details. This can be a good thing and it can be a bad thing. It all depends on what you expect your GUI to do.
For our purposes, the WM_HANDLEINPUT method is a very good thing. We want to know when a user clicks on a few of the buttons. We don't want to know if the user moves the window and it needs redrawing; that is handled for us.
It can be frustrating for beginners because you just don't know all the ins and outs. There will also appear to be conflicting advice floating around and conflicting examples. Rest assured, the information out there is correct. You just don't know how to tell the difference between a basic Intuition example, a basic BOOPSI example, a window.class example, a GadTools example, etc.
Context is Everything
When I say context I mean the AmigaOS Process or Task which is executing your code. This is important when working with a GUI. If you don't understand what context is running your code you will spend many hours fighting problems.
The only way to know for absolutely sure where you code is running is to find out for yourself:
struct Task *me = IExec->FindTask(NULL); IExec->DebugPrintF("me=%p name=%s\n", me, me->tc_Node.ln_Name);
Why is this so important? Because it dictates what you are allowed to do. For example, most GUI code runs on a Task named input.device which runs at priority 20. You can't do any DOS calls from a Task and you can't do too much when running at priority 20 or you will bog down the entire system.
GUI tool kits like MUI and ReAction have added mechanisms which move the processing off of input.device and back to your own Process. This is where the LAYOUT_DeferLayout tag comes in. Instead of doing GUI refreshing on the input.device Task it is deferred to your Process which is running at a much lower priority. This is all handled invisibly by the WM_HANDLEINPUT method call.
GUI tool kits also love to use Hooks and Callbacks. They enable an application to customize the behaviour of a GUI element without having to go through all the work of creating a new one. However, do you know what context that custom Hook code is running on? It makes a huge difference because you may need to create a Mutex to synchronize data or you may not be allowed to call any DOS functions. You won't be warned by the compiler so your application will just crash or worse, limp along and appear to work while corrupting data.
Custom gadgets and images
So what do you do when you want to have an area of the GUI where you draw whatever you want? What do you do when you want to be able to drag & drop in that special area?
The short answer is you need a custom BOOPSI object derived from gadgetclass to do things right. This is a rather mysterious thing to a majority of programmers so they tend to avoid it like the plague. Instead, they will use something like a space.gadget and try to handle everything using the application event handling.
We don't currently have a nice short example on how to create a custom class derived from gadgetclass yet. It is on the list of things to do because it is so very useful and a common problem for GUI creators.
It is time to modify this simple ProcTree example to do something it doesn't already do.
At this point we will work on doing something when the user clicks on an entry in the tree. One idea is to open a requester. Another would be to populate a display gadget with some information.