A Simple Application

Creating a small application before tackling a more complex one is a good way to gain familiarity with a new coding challenge. First, we tell you what our little application does and show you the code for it. After that we do a code walkthrough and point out important elements.

What the Application Does

Our Hello World application displays the words “Hello World” and provides a button to press. Pressing the button displays an alert, as shown in Figure 4.1, which is dismissed by tapping OK. There are two menus, each with one menu item (see Figure 4.2). As this is a very simple application, you just get a beep when you choose either menu item.

Dialog shown after tapping the button

Figure 4-1. Dialog shown after tapping the button

The menus of Hello World

Figure 4-2. The menus of Hello World

The Hello World Source Code

Now that you have an idea of what the application can do, look at Example 4.1 to see the code that produces it. Once you have looked through it for yourself, we will discuss it.

Example 4-1. The Hello World Source Code

#include <Pilot.h>  
#ifdef _     _GNUC_     _
#include "Callback.h"
#endif
#include "HelloWorldRsc.h"    

static Err StartApplication(void)
{
  FrmGotoForm(HelloWorldForm);
  return 0;
}

static void StopApplication(void)
{
}

static Boolean MyFormHandleEvent(EventPtr event)
{
  Boolean    handled = false;

#ifdef _     _GNUC_     _
   CALLBACK_PROLOGUE
#endif
   switch (event->eType) {
   case ctlSelectEvent:  // A control button was pressed and released.
      FrmAlert(GoodnightMoonAlert);
      handled = true;
      break;
    
   case frmOpenEvent:  
      FrmDrawForm(FrmGetActiveForm());
      handled = true;
      break;
      
   case menuEvent:    
      if (event->data.menu.itemID == FirstBeep)
         SndPlaySystemSound(sndInfo);
      else
         SndPlaySystemSound(sndStartUp);
      handled = true;
      break;
   }
#ifdef _     _GNUC_     _
   CALLBACK_EPILOGUE
#endif
   return handled;
}

static Boolean ApplicationHandleEvent(EventPtr event)
{
   FormPtr  frm;
   Int    formId;
   Boolean  handled = false;

   if (event->eType == frmLoadEvent) {
      //Load the form resource specified in the event then activate it
      formId = event->data.frmLoad.formID;
      frm = FrmInitForm(formId);
      FrmSetActiveForm(frm);

      // Set the event handler for the form.  The handler of the currently 
      // active form is called by FrmDispatchEvent each time it is called 
      switch (formId) {
      case HelloWorldForm:
         FrmSetEventHandler(frm, MyFormHandleEvent);
         break;
      }
      handled = true;
   }
  
   return handled;
}

static void EventLoop(void)
{
   EventType  event;
   Word      error;
  
   do {
    EvtGetEvent(&event, evtWaitForever);
    if (! SysHandleEvent(&event))
       if (! MenuHandleEvent(0, &event, &error))
          if (! ApplicationHandleEvent(&event))
             FrmDispatchEvent(&event);
   } while (event.eType != appStopEvent);
}

DWord PilotMain(Word launchCode, Ptr cmdPBP, Word launchFlags)
{
  Err err = 0;    
  
  if (launchCode == sysAppLaunchCmdNormalLaunch) { 
     if ((err = StartApplication()) == 0) {
        EventLoop();
        StopApplication();
     }
   }

   return err;
}

A Code Walkthrough of Hello World

Let’s start at the beginning with the #include files.

The #include files

Pilot.h is an include file that itself includes most of the standard Palm OS include files (using CodeWarrior, Pilot.h actually includes a prebuilt header file to speed compilation). To keep things simple, our application doesn’t use anything beyond the standard Palm OS include files. Indeed, any calls outside the standard ones would have necessitated the use of other specific Palm OS include files.

The second include file, Callback.h , defines some macros needed if you are using GCC. They are needed to handle callbacks from the Palm OS to your code. We discuss this in Section 4.2.3.11.

The third include file, HelloWorldRsc.h, defines constants for all the application’s resources (for example, HelloWorldForm). As we’ll see in Chapter 5, if you use Constructor, this file is generated automatically (see Example 4.2). If you use the GNU PalmPilot SDK, you usually create this file yourself (see Example 4.3).

Example 4-2. HelloWorldRsc.h Generated by Constructor (Used with CodeWarrior)

// Header generated by Constructor for Pilot 1.0.2
//
// Generated at 9:55:01 PM on Thursday, August 20, 1998
//
// Generated for file: Macintosh HD:Palm:HelloWorld:Rsc:Hello.rsrc
//
// THIS IS AN AUTOMATICALLY GENERATED HEADER FILE FROM CONSTRUCTOR FOR PALMPILOT;
// - DO NOT EDIT - CHANGES MADE TO THIS FILE WILL BE LOST
//
// Pilot App Name:      "Hello World"
//
// Pilot App Version:      "1.0"

// Resource: tFRM 1000
#define HelloWorldForm                            1000   
#define HelloWorldButtonButton                    1003   

// Resource: Talt 1101
#define GoodnightMoonAlert                        1101
#define GoodnightMoonOK                           0

// Resource: MBAR 1000
#define HelloWorldMenuBar                         1000

// Resource: MENU 1010
#define FirstMenu                                 1010
#define FirstBeep                                 1010

// Resource: MENU 1000
#define SecondMenu                                1000
#define SecondBeepmore                            1000

Example 4-3. HelloWorldRsc.h Created by Hand (Used with GNU PalmPilot SDK)

#define HelloWorldForm                            1000   
#define HelloWorldButtonButton                    1003   
#define HelloWorldMenuBar                         1000

#define GoodnightMoonAlert                        1101

#define FirstBeep                                 1010

#define SecondBeepmore                            1000

The main routine: PilotMain

Example 4.4 shows the main entry point into your application. The first parameter is the launch code. If your application is being opened normally, this parameter is the constant sysAppLaunchCmdNormalLaunch . The second and third parameters are used when the application is opened at other times.

Example 4-4. PilotMain

DWord PilotMain(Word launchCode, Ptr cmdPBP, Word launchFlags)
{
   Err err = 0;    
  
   if (launchCode == sysAppLaunchCmdNormalLaunch) {
      if ((err = StartApplication()) == 0) {
         EventLoop();
         StopApplication();
      }
   }

   return err;
}

If the launch code is sysAppLaunchCmdNormalLaunch, we do an initialization in StartApplication and run our event loop until the user does something to close the application. At that point, we handle termination in StopApplication.

The startup routine: StartApplication

In the routine shown in Example 4.5, we handle all the standard opening and initialization of our application. In more complicated applications, this would include opening our databases and reading user preference information. In our rudimentary Hello World application, all we need to do is tell the Form Manager that we want to send our (one and only) form. This queues up a frmLoadEvent in the event queue.

Example 4-5. StartApplication

static Err StartApplication(void)
{
   FrmGotoForm(HelloWorldForm);
   return 0;
}

The closing routine: StopApplication

Because we are creating such a simple application, we don’t actually have anything to do when it’s closing time. We provided the routine in Example 4.6 so that our Hello World source code would have the same standard structure as other Palm applications.

Example 4-6. An Empty StopApplication

static void StopApplication(void)
{
}

Normally in StopApplication we handle all the standard closing operations, such as closing our database, saving the current state in preferences, and so on.

The main event loop

In PilotMain, you will notice that after the initialization there is a call to the one main event loop (see Example 4.7). In this loop, we continually process events—handing them off wherever possible to the system. We go through the loop, getting an event with EvtGetEvent , and then dispatch that event to one of four nested event handlers, each of which gets a chance to handle the event. If an event handler returns true, it has handled the event and we don’t process it any further. EvtGetEvent then gets the next event in the queue, and our loop repeats the process.

The loop doggedly continues in this fashion until we get the appStopEvent , at which time we exit the function and clean things up in StopApplication.

Example 4-7. EventLoop

static void EventLoop(void)
{
   EventType  event;
   Word      error;
  
   do {
    EvtGetEvent(&event, evtWaitForever);             system routine

    if (! SysHandleEvent(&event))                    system routine

       if (! MenuHandleEvent(0, &event, &error)) system routine

          if (! ApplicationHandleEvent(&event))      routine we write

             FrmDispatchEvent(&event);               system routine

   } while (event.eType != appStopEvent);
}

Handling events with EvtGetEvent

This Event Manager routine’s sole purpose in life is to get the next event from the queue. It takes as a second parameter a time-out value (in ticks—hundredths of a second). EvtGetEvent returns either when an event has occurred (in which case it returns true) or when the time-out value has elapsed (in which case it returns false and fills in an event code of nilEvent).

We don’t have anything to do until an event occurs (this application has no background processing to do), so we pass the evtWaitForever constant, specifying that we don’t want a time-out.

The event queue and application event loop

Let’s step back for a moment and look at the events that are received from EvtGetEvent. Events can be of all different types, anything from low-level to high-level ones. In fact, one useful way to look at a Palm application is simply as an event handler—it takes all sorts of events, handing them off to various managers, which in turn may post a new event back to the queue, where it is handled by another event handler. We will discuss more sophisticated examples of this later (see Section 4.3 later in this chapter), but for now look at a very simple set of events to get an idea of how this all works together. Imagine the user has our application open and taps the stylus on the screen in the area of the silk-screened Menu button. The first time through the event queue the SysHandleEvent routine handles the event, interprets it, and creates a new event that gets put back in the queue (see Figure 4.3).

An event in the event loop

Figure 4-3. An event in the event loop

This new event, when it comes through the loop, gets passed through SysHandleEvent and on to the MenuHandleEvent , as it is now recognizable as a menu request (see Figure 4.4). MenuHandleEvent displays the menubar and drops down one of the menus. If the user now taps outside the menu, the menus disappear.

A regurgitated event in the event loop

Figure 4-4. A regurgitated event in the event loop

If a menu item is selected, however, a new event is generated and sent to the queue. This event is retrieved by the event loop, where it is passed through SysHandleEvent and on to MenuHandleEvent. Given the way this process works, you can see that the different managers are interested in different types of events. Keeping this in mind, let’s now return to our code and look at the event loop and the four routines in it.

SysHandleEvent

The first routine in the loop is always SysHandleEvent , as it provides functionality common to all Palm applications. For instance, it handles key events for the built-in application buttons. It does so by posting an appStopEvent to tell the current application to quit; the system can then launch the desired application.

It handles pen events in the silk-screened area (the Graffiti input area and the silk-screened buttons). For example, if the user taps on Find, SysHandleEvent completely handles the Find, returning only when the Find is done.

Here are some of the more important events it handles:

keyEvent

Occurs, among other times, when one of the built-in buttons is pressed. The keycode specifies which particular button is pressed. SysHandleEvent handles pen events in the Graffiti input area. When a character is written, SysHandleEvent posts a keyEvent with the recognized character.

penDownEvent

Occurs when the user presses the stylus to the screen.

penMoveEvent

Occurs when the user moves the stylus on the screen.

Note

penMoveEvents aren’t actually stored in the event queue, because there are so many of them. Instead, when EvtGetEvent is called, if no other events are pending, EvtGetEvent will return a penMoveEvent if the pen is down and has moved since the last call.

MenuHandleEvent

The second routine in our event loop is MenuHandleEvent . As you might have imagined, the MenuHandleEvent handles events involving menus. These events occur when a user:

  • Taps on the Menu silk-screened button. The function finds the menubar for the current form and displays it by creating a window.

  • Taps somewhere else while a menu is being displayed. The function closes the menu when the user taps outside it.

MenuHandleEvent also switches menus if the user taps on the menubar. As would be expected, it closes the menu and menubar if the user taps on a menu item. At this point, it posts a menu event that will be retrieved in a later call to EvtGetEvent.

ApplicationHandleEvent

The third routine, ApplicationHandleEvent , is also a standard part of the event loop and is responsible for loading forms and associating an event handler with the form. Note that this is also the first time our application is doing something with an event. Here is the code in our Hello World application for that routine:

static Boolean ApplicationHandleEvent(EventPtr event)
{
   FormPtr  frm;
   Int    formId;
   Boolean  handled = false;

   if (event->eType == frmLoadEvent) {
   // Load the form resource specified in the event and activate the form.
      formId = event->data.frmLoad.formID;
      frm = FrmInitForm(formId);
      FrmSetActiveForm(frm);

  // Set the event handler for the form.  The handler of the currently 
  // active form is called by FrmDispatchEvent each time it gets an event.
      switch (formId) {
      case HelloWorldForm:
         FrmSetEventHandler(frm, MyFormHandleEvent);
         break;
      }
      handled = true;
   }
   return handled;
}

While we’ll see a more complex example of ApplicationHandleEvent in Section 5.2, you can at least see that our routine handles the request to load our sole form.

Callbacks in GCC

We need to swerve down a tangent for a moment to discuss GCC. There is one way that your code will differ depending on whether you use GCC or CodeWarrior. Even if you’re not using GCC, it’s still worth reading this section to learn why we sprinkled a bunch of "#ifdef _ _GNUC_ _" in our functions.

The GCC compiler’s calling conventions differ from those in the Palm OS. In particular, the GCC compiler expects at startup that it can set up the A4 register (which it uses to access global variables) and that it will remain set throughout the life of the application. Unfortunately, this is not true when a GCC application calls a Palm OS routine that either directly or indirectly calls back to a GCC function (a callback).

The most common example of this occurrence is when we’ve installed an event handler for a form with FrmSetEventHandler . Once we’ve done that, a call to FrmDispatchEvent (a Palm OS routine) can call our form’s event handler (a GCC function, if we’ve compiled our application with GCC). At this point, if our event handler tries to access global variables, it’ll cause a spectacular application crash.

The solution is to use a set of macros that set the A4 register on entry to the callback function and restore it on exit. You need to provide a Callback.h header file as part of your project (see Example 4.8) and #include it in your file. Then, every callback needs to add the CALLBACK_PROLOGUE macro at the beginning of the callback function (just after variables are declared) and a CALLBACK_EPILOGUE macro at the end of the callback function. Here’s a very simple example:

static int MyCallback()
{
   int myReturnResult;
   int anotherVariable;
#ifdef _     _GNUC_     _
   CALLBACK_PROLOGUE
#endif
   // do stuff in my function
#ifdef _     _GNUC_     _
   CALLBACK_EPILOGUE
   #endif
   return myReturnResult;
}

It’s crucial that you don’t try to access global variables before the CALLBACK_ PROLOGUE macro. For example, here’s code that will blow up because you’re accessing globals before the macro has had a chance to set the A4 register:

static int MyCallback()
{
   int myVariable = gSomeGlobalVar;
#ifdef _     _GNUC_     _
   CALLBACK_PROLOGUE
#endif
   ...
}

It’s also important that you return from your function at the bottom. If you must ignore our advice and return from your function in the middle, make sure to add yet another instance of the CALLBACK_EPILOGUE right before the return.

Example 4-8. The Callback.h File, Needed for GCC

#ifndef __CALLBACK_H__
#define __CALLBACK_H__

/* This is a workaround for a bug in the current version of gcc: gcc assumes
   that no one will touch %a4 after it is set up in crt0.o. This isn't true
   if a function is called as a callback by something that wasn't compiled by
   gcc (like FrmCloseAllForms()).  It may also not be true if it is used as a
   callback by something in a different shared library. We really want a function
   attribute "callback" that inserts this prologue and epilogue automatically.
- Ian */

register void *reg_a4 asm("%a4");

#define CALLBACK_PROLOGUE \
   void *save_a4 = reg_a4; asm("move.l %%a5,%%a4; sub.l #edata,%%a4" : :);
#define CALLBACK_EPILOGUE reg_a4 = save_a4;

#endif

Note

There’s been some discussion among those who use the GCC compiler about a more convenient solution to the Example 4.8 workaround. Some folks want to get rid of the macros by modifying the GCC compiler with a callback attribute to the function declaration. This would cause the compiler to add code that manages A4 correctly. Here’s an example:

callback int MyCallback()
{
   // code which can safely access globals
}

Others want a more radical solution. They want to be able to use all functions as callbacks without any special declaration.

FrmDispatchEvent

This fourth and last routine in the event loop is the one that indirectly provides form-specific handling. This routine handles standard form functionality (for example, a pen-down event on a button highlights the button, a pen-up on a button posts a ctlSelectEvent to the event queue). Cut/copy/paste in text fields are other examples of functionality handled by FrmDispatchEvent. In order to provide form-specific handling, FrmDispatchEvent also calls the form’s installed event handler. Therefore, when FrmDispatchEvent gets an event, it calls our own MyFormHandleEvent routine:

static Boolean MyFormHandleEvent(EventPtr event)
{
   Boolean    handled = false;

   switch (event->eType) {
    case ctlSelectEvent:  // A control button was pressed and released.
       FrmAlert(GoodnightMoonAlert);
       handled = true;
       break;
    
    case frmOpenEvent:  
       FrmDrawForm(FrmGetActiveForm());
       handled = true;
       break;
      
    case menuEvent:    
       if (event->data.menu.itemID == FirstBeep)
          SndPlaySystemSound(sndInfo);
       else
          SndPlaySystemSound(sndStartUp);
      handled = true;
      break;
   }
   return handled;
}

As the code indicates, we take an action if the user taps on our button or chooses either of the two menu items. We beep in either case.

Hello World Summary

In this simple application, we have all the major elements of any Palm application. In review, these are:

  • A set of necessary include files

  • A startup routine called StartApplication, which handles all our initial setup

  • A PilotMain routine, which starts an event loop to handle events passed to it by the system

  • An event loop, which continually hands events to a series of four managing routines—SysHandleEvent, MenuHandleEvent, ApplicationHandleEvent, and FrmDispatchEvent

  • A set of routines to handle our form-specific functionality

  • A closing routine called StopApplication, which handles the proper closing of our application

Get Palm Programming: The Developer's Guide now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.