Search the Catalog
Palm OS Programming, 2nd Edition: The Developer's Guide

Palm OS Programming, 2nd Edition

The Developer's Guide

By Neil Rhodes & Julie McKeehan
2nd Edition October 2001
1-56592-856-3, Order Number: 8563
702 pages, $39.95

Chapter 5
Structure of an Application

In this chapter:
Terminology
Palm OS Conventions
The Palm OS and an Application
A Simple Application--OReilly Starter
Other Times Your Application Is Called
Examples
What to Remember

Before you can write a Palm application, you need to know how it interacts with the OS and how it is organized. Prior to this discussion, we cover the standard terminology and naming conventions within the Palm OS. Once you know what the words mean, we can talk about the application and the OS.

We start with a discussion of how an application is structured to run on the Palm OS. You will learn that a Palm application is an event-driven system and that its routines are structured to handle various types of events. We will describe an application's life cycle--its starting, running, and closing. To help solidify your understanding of these points, we provide a simple application, OReilly Starter, which is a prototypical Palm application. We walk you through its organization (for example, its source files, utility files, and so on) and then show you the source code in its routines. While the application doesn't do much, it contains all of the standard routines and has the correct structure for any Palm application. You can use it as the starting point for your own work.

Typically, an application launches when a user opens it; you will learn how to handle this. We will also discuss other times the OS may access an application and what you need to do about it. These instances require you to structure your application so that it can provide information or launch as necessary. Lastly, there are some tricks you might want to add that allow shortcut access from within your application (for example, a hard button). Or, you might want to be a tyrant and take over the unit completely, denying access to other applications while your application is running.

Terminology

Like every operating system, the Palm OS has it own set of necessary terminology for you to learn. Much of it may be familiar to you already from other systems on which you have worked. We suggest that you skim through the list and concentrate on the items that are new to you. New and unique terms are listed first.

Form
An application form (what many people would think of as a window) that usually covers the entire screen (modal forms cover only the bottom part of the screen, however). A form optionally contains controls, text areas, and menus. In a Palm OS application there is only one active form allowed at a time. Chapter 8 covers forms in detail.

Window
A rectangular area in which things like dialog boxes, forms, and menus are drawn by the application. The Window Manager makes sure that windows display properly relative to each other (for example, it has the ability to restore the old contents when a window is closed).

Database
A collection of persistent memory chunks. There are two kinds:

Resource
A piece of data stored in a resource database. Each resource is identified by a resource type and number. Note that a Palm application is simply a collection of resources. Chapter 8 covers resources in more detail.

Record
A memory chunk identified by a unique record ID located in a database. Applications typically store data in record databases.

Event
A data structure that describes things that happen in an application. These can be low-level hardware events such as a pen down, pen up, or hardware key press. They can also be higher-level events such as a character entry, a menu item selection, or a software button press.

The Palm OS is an event-driven system. Only one application is open at a time. When that application is running, it runs an event loop that retrieves events and continues to handle them until the user starts another application.

Main event loop
The main loop of execution in an application that repeatedly retrieves events, and then acts on them.

Launch code
A parameter passed to an application that specifies what the application should do when that particular launch code is executed. An application typically handles more than one launch code. This is the communication method used between the OS and a non-running application and between applications.

Menu
Menus are stored in resources grouped together into menu bars and are displayed when the user taps the menu area. See Chapter 11 for more details.

Menu bar
A collection of menus stored in a resource. Each form can have a menu bar associated with it.

Dialog box
A window containing controls that require the user to make a decision. In other words, the dialog box must be dismissed (usually by tapping on one of its buttons) before the application can continue.

Alert
A simple dialog box with only an icon, text, and buttons.

Palm OS Conventions

There are also a variety of Palm coding conventions that are useful to know. There are type conventions and standard naming practices for everything from functions to managers. It is worth getting a clearer idea of what these are before wading knee-deep into your first coding project. These are the ones you should learn.

Types

Here are the main types used by the Palm OS:

UInt32
An unsigned 32-bit integer.

Int32
A signed 32-bit integer.

UInt16
An unsigned 16-bit integer.

Int16
A signed 16-bit integer.

UInt8
An unsigned 8-bit integer.

Int8
A signed 8-bit integer.

Boolean
A 1-byte true or false value.

Char
A 1-byte character that will only work on systems with 1-byte encodings.

WChar
A 2-byte character suitable for all encodings (including for Japanese, Chinese, and so on).

Err
A 2-byte integer used for errors. The value errNone signifies no error.

Coord
A signed 2-byte integer used to represent a screen or window coordinate.

MemPtr
Specifies a (4-byte) pointer to an allocated chunk in memory.

MemHandle
A (4-byte) reference to a relocatable chunk of memory (see Chapter 6 for more details).

Function and Manager Naming Conventions

The Palm OS uses mixed-case for names, with an initial uppercase for functions and types. Constants and enumerations begin with lowercase letters. The following snippet of code shows the conventions in action (emphasis shows functions, types, and enumerations):

FormPtr form;
UInt32  romVersion;
FtrGet(sysFtrCreator, sysFtrNumROMVersion, &romVersion);
FrmSetEventHandler(form, MyHandler);

The Palm OS is divided into functional areas called managers. Each manager has its own header file (for example Form.h). Every routine in that manager usually begins with a manager abbreviation (for example: EvtGetEvent is part of the Event Manager, declared in Event.h; FrmGotoForm is part of the Form Manager, declared in Form.h). Table 5-1 contains example abbreviations.

Table 5-1: Standard manager abbreviations

Abbreviation

Manager

Alm

Alarm Manager

Dm

Data Manager

Evt

Event Manager

Ftr

Feature Manager

Mem

Memory Manager

Snd

Sound Manager

Str

String Manager

Sys

System Manager

Txt

Text Manager

C Library Conventions

You will not normally use standard C library routines like strlen or memcpy. While these functions are available as libraries, using them in your code bloats the size of your application. Since space is always tight, you have been provided with another way to use such routines; you'll use the ones in ROM that are small and quick. Table 5-2 shows the Palm OS-equivalents for standard C routines.

Table 5-2: Equivalents to standard C routines

Standard C routine

Palm OS routine

Additional information

strlen

StrLen

 

strcpy

StrCopy

 

strncpy

StrNCopy

Doesn't pad with extra null terminators.

strcat

StrCat

 

strncat

StrNCat

Last parameter is the total length of the string (including null terminator) rather than the number of characters to copy. Doesn't append an extra null terminator if source string is empty.

strcmp

StrCompare

 

strncmp

StrNCompare

 

itoa

StrIToA

 

strchr

StrChr

 

strstr

StrStr

 

sprintf

StrPrintF

Limited subset of sprintf, for example, no %f.

svprintf

StrVPrintF

Limited subset of svprintf, for example, no %f.

malloc

MemPtrNew

Although you'll probably use handles instead.

free

MemPtrFree

 

memmove

MemMove

 

memset

MemSet

Warning: the last two parameters have been reversed!

memcmp

MemCmp

 

That sums up the important conventions you need to know about to create a Palm application.

The Palm OS and an Application

When the Palm OS wants to communicate with an application that may not be running, it calls the application's PilotMain routine.

The Main Routine: PilotMain

This is the main entry point into a Palm OS application; it is always a function named PilotMain. Given its responsibilities, it is worth looking at this routine in some detail. First, we'll start with the parameters, and then we'll show you the code. Following the code is a walk-through discussion of what is happening.

A quick look at PilotMain

The first parameter is the launch code, which specifies why the function is being called. Whenever your application is being opened normally, this parameter will be the constant sysAppLaunchCmdNormalLaunch. The second and third parameters are used when the application is opened at other times (see Example 5-1).

Example 5-1: Typical PilotMain (app-specific portions are emphasized)

UInt32 PilotMain(UInt16 launchCode, MemPtr launchParameters, 
  UInt16 launchFlags)
{
#pragma unused(launchParameters)
  Err error;
 
  switch (launchCode) {
  case sysAppLaunchCmdNormalLaunch:
    error = RomVersionCompatible(kOurMinVersion, launchFlags);
    if (error != errNone) 
      return error;
    error = AppStart(  );
    if (error != errNone) 
      return error;
      
    FrmGotoForm(MainForm);
    AppEventLoop(  );
    AppStop(  );
    break;
 
  default:
    break;
  }
  
  return errNone;
}

If the launch code is sysAppLaunchCmdNormalLaunch, we first check to make sure that the version of the device we are running on is one that we support (you'll have to define the minimum version appropriate for your application).

TIP:   A pragma is a compiler-specific directive. The #pragma unused(launchParameters) is an indication to CodeWarrior that the parameter launchParameters is not used in the function. With this in place, CodeWarrior won't be constantly warning us of the unused parameter. GCC (as it should) ignores the pragma.

While we will talk about each of the routines called in our PilotMain in greater detail in just a moment, briefly, this is what they do. First, we call our own routine, AppStart, which does application-specific initialization. The call to FrmGotoForm specifies that the MainForm will initially be displayed (if we left that out, the display would be blank since no form was opened). It will queue a frmLoadEvent in the Event Manager's event queue (we'll talk more about form events in Chapter 8).

Now, the application's motor--our event loop, AppEventLoop--runs until the user does something to close the application. At that point, we handle termination in AppStop.

The Startup Routine: AppStart

Here is where we handle all the standard opening and initialization of our application. In a typical application, this would include opening our databases and reading user preference information. In our skeleton application (OReilly Starter), we won't do anything.

Although it's common to name this routine AppStart, it's only a convention, not a requirement like the name of PilotMain.

TIP:   Note that we call FrmGotoForm in our PilotMain rather than here. This is important because an application will eventually need to support Find. In that case, another launch code will require us to initially open a different form. Because of this, we need FrmGotoForm to be in a location that allows us to switch between forms depending on how the application gets opened.

Since we have a very simple application, our AppStart does nothing:

static Err AppStart(void)
{
   return 0;
}

Note that we have a static declaration here for the benefit of CodeWarrior. If we leave off the static, then CodeWarrior will warn us that the function doesn't have a prototype.

CodeWarrior complains about this potential error because the function might have been declared in a header file that wasn't included in this file. Furthermore, that declaration might declare the function differently (for example, different numbers or types of parameters). Specifying that the function is static guarantees that the function won't be called from outside this file, thus no separate function declaration is required.

In general, always add a static when defining functions that are only used within a single file. This is the purpose for the keyword after all.

The Closing Routine: AppStop

Normally, in AppStop we handle all the standard closing operations, such as closing our database, saving the current state in preferences, and so on. Because we are creating such a simple application, we don't actually have to do those things. However, like all applications, ours needs to make sure that any open forms are closed. FrmCloseAllForms will do that closing.

static void AppStop(void)
{
  FrmCloseAllForms(  );
}

The Main Event Loop

In PilotMain, after the initialization there is a call to the one main event loop, AppEventLoop. In brief, this is what happens in this loop:

Example 5-2 shows the code for a typical AppEventLoop.

Example 5-2: Typical AppEventLoop

void AppEventLoop(void)
{
  Err error;
  EventType event;
 
  do {
    EvtGetEvent(&event, evtWaitForever);
    
    if (! SysHandleEvent(&event))
      if (! MenuHandleEvent(0, &event, &error))
        if (! AppHandleEvent(&event))
          FrmDispatchEvent(&event);
 
  } while (event.eType != appStopEvent);
}

Handling events with EvtGetEvent

This Event Manager routine's sole job is to get the next event from the queue. It takes as a second parameter a timeout value (in ticks--SysTicksPerSecond returns the units, which is 100 on all devices as of mid-2001). EvtGetEvent returns when either an event has occurred, or when the timeout value has elapsed (in which case it fills in an event code of nilEvent).

TIP:   EvtGetEvent can actually return with a nilEvent at any time, regardless of whether the timeout value has elapsed.

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

The Event Queue and Application Event Loop

At this point, it is worth looking in more detail 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 hands all sorts of events off to various managers, which in turn may post new events back to the queue where they will get handled by other event handlers. We will discuss more sophisticated examples of this later (see "Scenarios" later in this chapter), but for now we will look at a very simple set of events to get an idea of how all this 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 5-1).

When this new event comes through the loop, it will get passed through SysHandleEvent and on to the MenuHandleEvent as it is now recognizable as a menu request (see Figure 5-2). MenuHandleEvent will display the menu bar and drop down one of the menus. If the user now taps outside of the menu, the menus will disappear.

Figure 5-1. An event in the event loop

 

Figure 5-2. A regurgitated event in the event loop

 

If a menu item is selected, however, then a new event is generated and posted to the queue. This event is retrieved by the event loop where it is passed through SysHandleEvent, and then on to MenuHandleEvent, and continues until some code handles the menu item. 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.

The Four Routines of the Main Event Loop

You have briefly seen the four routines and their order in the main event loop. Here is a more detailed look at the responsibilities of each routine.

SysHandleEvent

if (! SysHandleEvent(&event))

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

It also 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 will completely handle the Find, returning only when the Find is done.

SysHandleEvent likewise handles the Power key, dealing with putting the device to sleep and/or turning backlighting on or off, depending on the duration of the Power key press.

MenuHandleEvent

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

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 following:

As would be expected, it closes the menu and menu bar 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.

The first parameter to MenuHandleEvent specifies which menu to handle (0 means the current menu and is what you'll always pass). The last parameter is a pointer to an error return result; note, however, that virtually all applications (including ours) ignore the error result.

Unlike all other managers, the Menu Manager routines use a four-character prefix (Menu) rather than the three characters common to the others. In addition, unlike SysHandleEvent and FrmDispatchEvent, which take only the event and return a Boolean, MenuHandleEvent takes two extra parameters. (We get the feeling that whoever wrote the Menu Manager skipped some of the meetings on API design that the rest of the OS team attended.)

AppHandleEvent

if (! AppHandleEvent(&event))

The third routine, AppHandleEvent, is also a standard part of the event loop and is responsible for loading forms and associating an event handler with the form. This routine handles any events that it wants to process application-wide (as opposed to the majority of events, which are form-specific). This code is not part of the OS; you write it. However, it will be very similar from application to application. Example 5-3 shows a typical example of the routine.

Example 5-3: Typical AppHandleEvent (app-specific portions are emphasized)

static Boolean AppHandleEvent(EventPtr event)
{
  UInt16 formId;
  FormPtr form;
 
  if (event->eType == frmLoadEvent) {
    // Load the form resource.
    formId = event->data.frmLoad.formID;
    form = FrmInitForm(formId);
    ErrFatalDisplayIf(!form, "Can't initialize form");
    FrmSetActiveForm(form);
 
    // Set the event handler for the form.  The handler of the currently
    // active form is called by FrmHandleEvent each time it receives an event.
    switch (formId) {
    case MainForm:
      FrmSetEventHandler(form, MainFormHandleEvent);
      break;
 
    case SecondForm:
      FrmSetEventHandler(form, SecondFormHandleEvent);
      break;
 
    default:
      ErrFatalDisplay("Invalid Form Load Event");
      break;
 
    }
    return true;
  } else 
    return false;
}

A frmLoadEvent is a request to load a particular form. It is our responsibility to load the requested form (the OS won't load a form automatically). We initialize the specified form ID (FrmInitForm) and then make it the one-and-only active form (FrmSetActiveForm). Finally, we set the form's event handler (FrmSetEventHandler). If you create a new form, you'll write an event handler for it, and then add a new case to the switch statement, setting the event handler.

FrmDispatchEvent

FrmDispatchEvent(&event);

This fourth and last routine in the event loop is the one that indirectly provides form-specific handling. FrmDispatchEvent calls FrmHandleEvent, which provides standard form functionality (for example, a pen-down event on a button highlights the button, a pen-up on a button posts a ctlSelectEvent event to the event queue). Cut, copy, and paste in text fields are other examples of functionality handled by FrmHandleEvent.

In order to provide form-specific handling, FrmDispatchEvent calls the form's installed event handler first before calling FrmHandleEvent. If the event handler returns true, no further processing happens. If it returns false, FrmDispatchEvent will then call FrmHandleEvent, which will provide the standard functionality. Thus, your event handler always gets the first crack at events occurring within a form. Here's pseudocode for FrmDispatchEvent to give you an idea of what it will look like:

Boolean FrmDispatchEvent(EventType *event)
{
  Boolean handled = result of calling Form's event handler;
  if (handled)
    return true;
  else
    return FrmHandleEvent(event);
}

A Simple Application--OReilly Starter

Now it's time to apply what you just learned about an application's interaction with the OS, its PilotMain routine, and the event loop to an actual application. We are using OReilly Starter, the application we first discussed in Chapter 4.

What the Application Does--Its User Interface

Our OReilly Starter application has two forms. On the first form, there are two buttons. Pressing the first one causes the Palm device to beep. Pressing the second button switches the view to the second form. The second form has a single button that returns to the previous form (see Figure 5-3). This is all there is to our simple application.

Figure 5-3. The OReilly Starter application

 

An Overview of the Source Files

Here are the source files that make up this application (as opposed to the tools-specific files like CodeWarrior .mcp files or PRC-Tools .def files).

Main.c
This file contains the main entry point (PilotMain) of the application. It also includes the event loop, application startup and shutdown code, and the code to load forms as needed.

MainForm.c
This contains the code that handles everything that occurs in the first form.

SecondForm.c
This contains the code for the second form.

Utils.c
This contains some utility routines that can be used throughout the application. You can include this same source file in all your applications.

Resources.rcp
This PilRC file contains the UI elements (the forms, form objects, and the application name).

ResourceDefines.h
This file defines constants for all the application's resources. This file is included by the .c files, as well as Resources.rcp.

MainForm.h
This declares the event handler for the main form. This is included by both Main.c and MainForm.c.

SecondForm.h
This declares the event handler for the second form. It is also included by both Main.c and SecondForm.c.

Constants.h
This file contains the defined constants used throughout the application.

Utils.h
This declares the utility functions in Utils.c.

The Source File of Main.c

Main.c starts with the #include files (see Example 5-4).

Example 5-4: First part of Main.c: #defines and #includes

#define DO_NOT_ALLOW_ACCESS_TO_INTERNALS_OF_STRUCTS
#include <BuildDefines.h>
#ifdef DEBUG_BUILD
#define ERROR_CHECK_LEVEL ERROR_CHECK_FULL
#endif
#include <PalmOS.h>
#include "ResourceDefines.h"
#include "MainForm.h"
#include "SecondForm.h"
#include "Utils.h"
#include "Constants.h"

TIP:   PalmOS.h is an include file that contains most of the standard Palm OS include files. By default, it defines an ERROR_CHECK_LEVEL of ERROR_CHECK_PARTIAL, which is suitable for a release build. It doesn't, however, provide the checking we'd like for a debug build (see Chapter 7 for more information on debug builds). Thus, if we're compiling a DEBUG_BUILD, we redefine ERROR_CHECK_LEVEL. We must #include BuildDefines.h first, in order to obtain the definition of ERROR_CHECK_FULL.

We define DO_NOT_ALLOW_ACCESS_TO_INTERNALS_OF_STRUCTS to help with good coding practices. This will cause the 4.0 SDK to generate a compile-time error for us if we try to access fields within OS structures directly, rather than through appropriate API calls. You should always use the APIs, rather than fool around within an OS structure directly.

Example 5-5 shows the remainder of Main.c.

Example 5-5: Remaining functions in Main.c

static Boolean AppHandleEvent(EventPtr event)
{
  UInt16 formId;
  FormPtr form;
 
  if (event->eType == frmLoadEvent) {
    // Load the form resource.
    formId = event->data.frmLoad.formID;
    form = FrmInitForm(formId);
    ErrFatalDisplayIf(!form, "Can't initialize form");
    FrmSetActiveForm(form);
 
    // Set the event handler for the form.  The handler of the currently
    // active form is called by FrmHandleEvent each time it receives an event.
    switch (formId) {
    case MainForm:
      FrmSetEventHandler(form, MainFormHandleEvent);
      break;
 
    case SecondForm:
      FrmSetEventHandler(form, SecondFormHandleEvent);
      break;
 
    default:
      ErrFatalDisplay("Invalid Form Load Event");
      break;
 
    }
    return true;
  } else 
    return false;
}
 
static void AppEventLoop(void)
{
  Err error;
  EventType event;
 
  do {
    EvtGetEvent(&event, evtWaitForever);
    
    if (! SysHandleEvent(&event))
      if (! MenuHandleEvent(0, &event, &error))
        if (! AppHandleEvent(&event))
          FrmDispatchEvent(&event);
 
  } while (event.eType != appStopEvent);
}
 
 
static Err AppStart(void)
{
   return errNone;
}
 
 
static void AppStop(void)
{
  FrmCloseAllForms(  );
}
 
UInt32 PilotMain(UInt16 launchCode, MemPtr launchParameters, 
  UInt16 launchFlags)
{
#pragma unused(launchParameters)
  Err error;
 
  switch (launchCode) {
  case sysAppLaunchCmdNormalLaunch:
    error = RomVersionCompatible (kOurMinVersion, launchFlags);
    if (error) 
      return error;
    error = AppStart(  );
    if (error) 
rror;
      
    FrmGotoForm(MainForm);
    AppEventLoop(  );
    AppStop(  );
    break;
 
  default:
    break;
  }
  
  return errNone;
}

kOurMinVersion is defined as Version 3.0 of the Palm OS in Constants.h.

The Form Files of the Application

Main.c is the source file containing routines responsible for handling the main form (see Example 5-6).

Example 5-6: Main.c

/*
 Copyright (c) 2000-2001, Neil Rhodes and Julie McKeehan
 		neil@pobox.com
 All rights reserved.
 
From the book "Palm OS Programming (2nd edition)" by O'Reilly.
 
Permission granted to use this file however you see fit.
*/
 
#define DO_NOT_ALLOW_ACCESS_TO_INTERNALS_OF_STRUCTS
#include <BuildDefines.h>
#ifdef DEBUG_BUILD
#define ERROR_CHECK_LEVEL ERROR_CHECK_FULL
#endif
#include <PalmOS.h>
#include "ResourceDefines.h"
#include "MainForm.h"
 
 
static void MainFormInit(FormPtr form)
{
#pragma unused(form)
  // Warning-- don't do any drawing in this routine.
  // Also, don't call FrmSetFocus from here (it must be called *after*
  // FrmDrawForm).
}
 
static void MainFormDeinit(FormPtr form)
{
#pragma unused(form)
}
 
 
Boolean MainFormHandleEvent(EventPtr event)
{
  Boolean handled = false;
  FormPtr form;
 
  switch (event->eType) 
  {
  case frmOpenEvent:
    form = FrmGetActiveForm(  );
    MainFormInit(form);
    FrmDrawForm(form);
    // Here's where you'd add a call to FrmSetFocus.
    handled = true;
    break;
      
      
  case ctlSelectEvent:
  switch (event->data.ctlSelect.controlID) {
    case MainBeepButton:
      SndPlaySystemSound(sndWarning);
      handled = true;
      break;
        
    case MainGotoSecondFormButton:
      FrmGotoForm(SecondForm);
      handled = true;
      break;
    }
    break;
 
  case frmCloseEvent:
    MainFormDeinit(FrmGetActiveForm(  ));
    handled = false;
    break;
 
  default:
    break;
  }  
  return handled;
}

The event handler for the main form handles three different kinds of events:

The MainFormInit and MainFormDeinit routines are there because it is quite common to need to do some initialization when a form opens. You will often need to do some cleanup when a form closes. This is where such things should happen.

The MainFormHandleEvent handles the ctlSelectEvent by looking to see what control was chosen:

The EventType data structure

In order to handle events within your form's event handler, you have to become familiar with the EventType data structure (the data that comes back from EvtGetEvent). Here are the fields within that structure:

eType
The type of the event (an enumeration: examples are menuEvent, penDownEvent, keyDownEvent, and so on).

penDown
Was the stylus down when this event occurred?

tapCount
For OS 3.5 and later, the number of successive taps. On a double-tap, you'll get two events. The first will have a tapCount of 1, and the second a tapCount of 2. Note: the value in this field is undefined prior to the 3.5 version of the OS.

screenX
The horizontal location of the stylus (in screen coordinates).

screenY
The vertical location of the stylus (in screen coordinates).

Each event type also has specific associated data. For example, menu events have the menu item ID, control events have the control ID, key events have the character. This data is stored in a union of structures, with a separate structure defined for each kind of event. The union of structures' name is data. Within that union, the name of the structure is the name of the event type, without the event suffix. For example, the data for a menuEvent is found in the data.menu structure, the data for a ctlSelectEvent is found in data.ctlSelect.

Example 5-7 shows SecondForm.c, which contains the code responsible for the second form.

Example 5-7: SecondForm.c

/*
 Copyright (c) 2000-2001, Neil Rhodes and Julie McKeehan
   neil@pobox.com
 All rights reserved.
 
From the book "Palm OS Programming (2nd edition)" by O'Reilly.
 
Permission granted to use this file however you see fit.
*/
 
#define DO_NOT_ALLOW_ACCESS_TO_INTERNALS_OF_STRUCTS
#include <BuildDefines.h>
#ifdef DEBUG_BUILD
#define ERROR_CHECK_LEVEL ERROR_CHECK_FULL
#endif
#include <PalmOS.h>
#include "ResourceDefines.h"
#include "SecondForm.h"
 
 
static void SecondFormInit(FormPtr form)
{
#pragma unused(form)
  // Warning-- don't do any drawing in this routine.
  // Also, don't call FrmSetFocus from here (it must be called *after*
  // FrmDrawForm).
}
 
static void SecondFormDeinit(FormPtr form)
{
#pragma unused(form)
}
 
 
Boolean SecondFormHandleEvent(EventPtr event)
{
  Boolean handled = false;
  FormPtr form;
 
 
  switch (event->eType) {
  case frmOpenEvent:
    form = FrmGetActiveForm(  );
    SecondFormInit(form);
    FrmDrawForm(form);
    // Here's where you'd add a call to FrmSetFocus.
    handled = true;
    break;
      
      
  case ctlSelectEvent:
    switch (event->data.ctlSelect.controlID) {
    case SecondGotoMainFormButton:
      FrmGotoForm(MainForm);
      handled = true;
      break;
    }
    break;
 
  case frmCloseEvent:
    SecondFormDeinit(FrmGetActiveForm(  ));
    handled = false;
    break;
 
  default:
    break;
  }
  
  return handled;
}

This code is very similar in structure to that of MainForm.c. The difference is what happens when a control is chosen.

The Utility and Resource Files

Here is a look at the utility and resource files that OReilly Starter uses.

Utils.c contains several utility routines in this source file, but we use only one in this application. Example 5-8 shows that sole routine.

Example 5-8: Utils.c (abridged to show only the routine we use)

/*
 Copyright (c) 2000-2001, Neil Rhodes and Julie McKeehan
    neil@pobox.com
 All rights reserved.
 
From the book "Palm OS Programming (2nd edition)" by O'Reilly.
 
Permission granted to use this file however you see fit.
*/
 
#define DO_NOT_ALLOW_ACCESS_TO_INTERNALS_OF_STRUCTS
#include <BuildDefines.h>
#ifdef DEBUG_BUILD
#define ERROR_CHECK_LEVEL ERROR_CHECK_FULL
#endif
#include <PalmOS.h>
#include "Utils.h"
#include "ResourceDefines.h"
 
Err RomVersionCompatible(UInt32 requiredVersion, UInt16 launchFlags)
{
  UInt32 romVersion;
 
  // See if we're on minimum required version of the ROM or later.
  FtrGet(sysFtrCreator, sysFtrNumROMVersion, &romVersion);
  if (romVersion < requiredVersion) {
    UInt16 safeToCallAlertFlags;
    
    safeToCallAlertFlags = 
      sysAppLaunchFlagNewGlobals | sysAppLaunchFlagUIApp;
    if ((launchFlags & (safeToCallAlertFlags)) == safeToCallAlertFlags) {
      FrmAlert (RomIncompatibleAlert);
    
      // Pilot 1.0 will continuously relaunch this application unless we switch to 
      // another safe one.
      if (romVersion < sysMakeROMVersion(2,0,0,sysROMStageRelease,0))
        AppLaunchWithCommand(sysFileCDefaultApp, 
          sysAppLaunchCmdNormalLaunch, NULL);
    }
    
    return (sysErrRomIncompatible);
  }
 
  return errNone;
}

You'll normally use this routine as-is in every application you write. It works by calling the Feature Manager (which contains runtime information about the Palm device) to find out what ROM version this Palm device is running. If the version is not earlier than the requested one, the routine returns no error. Otherwise, it will display an alert and return an error. Last of all, it deals with a potentially infinite relaunch problem in the 1.0 version of the OS by explicitly launching some other application.

The Resources.rcp PilRC file, shown in Example 5-9, can be compiled by either the PilRC command line, when using the PRC-Tools, or by the PilRC plug in, when using the CodeWarrior environment.

Example 5-9: Resources.rcp

#include "ResourceDefines.h"
 
APPLICATIONICONNAME 1000 "Starter"
 
ALERT ID RomIncompatibleAlert
CONFIRMATION
BEGIN
  TITLE "System Incompatible"
  MESSAGE "System Version 3.0 or greater " \
    "is required to run this application."
  BUTTONS "OK" 
END
 
FORM ID MainForm AT (0 0 160 160)
USABLE
BEGIN
  TITLE "OReilly Starter Main"
  BUTTON "Beep" ID MainBeepButton AT (40 100 AUTO AUTO)
  BUTTON "Goto Second Form" ID MainGotoSecondFormButton AT 
    (PrevLeft PrevBottom + 5 AUTO AUTO)
END
 
FORM ID SecondForm AT (0 0 160 160)
USABLE
BEGIN
  TITLE "OReilly Starter Second"
  BUTTON "Goto Main Form" ID SecondGotoMainFormButton AT 
    (40 130 AUTO AUTO)
END

This defines the name of the application as it appears in the Application launcher, the alert that's shown if run on an older system, and the two forms.

Header Files

Example 5-10 shows Constants.h, which defines two constants: our creator ID and the minimum version for which we'll run.

Example 5-10: Constants.h

#ifndef CONSTANTS_H
#define CONSTANTS_H
 
#define kAppFileCreator         '????'
 
// The minimum OS version we support.
#define kOurMinVersion  sysMakeROMVersion(3,0,0,sysROMStageRelease,0)
#endif

The next file, ResourceDefines.h, is included by both the .c files and the .rcp file to define the constants used by the UI (see Example 5-11).

Example 5-11: ResourceDefines.h

#define RomIncompatibleAlert           1001
 
#define MainForm                       2000 
#define MainBeepButton                 2001
#define MainGotoSecondFormButton       2002
 
#define SecondForm                     3000 
#define SecondGotoMainFormButton       3001

MainForm.h file defines the function that is exported from MainForm.c (see Example 5-12).

Example 5-12: MainForm.h

#ifndef MAINFORM_H
#define MAINFORM_H
 
Boolean MainFormHandleEvent(EventPtr event);
 
#endif

SecondForm.h file defines the function that is exported from SecondForm.c (see Example 5-13).

Example 5-13: SecondForm.h

#ifndef SECONDFORM_H
#define SECONDFORM_H
 
Boolean SecondFormHandleEvent(EventPtr event);
 
 
#endif

Utils.h exports some useful utility routines (see Example 5-14).

Example 5-14: Utils.h

#ifndef UTILS_H
#define UTILS_H
 
Err RomVersionCompatible(UInt32 requiredVersion, UInt16 launchFlags);
void *GetObjectPtr(FormPtr form, UInt16 objectID);
void *GetObjectPtrFromActiveForm(UInt16 objectID);
 
#endif

OReilly Starter Summary

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

Other Times Your Application Is Called

You know how an application works within the OS and now you know about the organizational structure and content of the various source files used to create an application. The last remaining piece of the puzzle is other times the OS communicates with an application. Let's turn to the details of this.

The Palm OS makes a distinction between communicating with the active application and communicating with a possibly nonactive application. In this first case, the active application is busy executing an event loop, and can be communicated with by posting events to the event queue. As shown earlier in our example, this was how our application got closed; the appStopEvent was posted to the event queue. When the active application gets that event, it quits.

Because there are other times that your application gets called by the Palm OS, there needs to be a way to communicate with it in those instances as well. First, let's look at a partial list of the circumstances under which the system might want to talk to your application:

In all these cases, a communication must take place to a nonactive or closed application. The question is: how does the system do this? The answer: its PilotMain routine is called with different launch codes.

Launch Codes

Within the Palm OS, it is the launch code that specifies to the application which of the previously mentioned circumstances exists and what the application needs to do. These codes arrive at the application's PilotMain routine by way of its launchCode parameter. Here are some common launch codes:

sysAppLaunchCmdFind
This code tells the application to look up a particular text string and return information about any matching data. Called by the system when the user does a Find.

sysAppLaunchCmdGoTo
This code tells the application to open if it isn't already open and then to go to the specified piece of data. Called by the system when the user taps on a found item.

sysAppLaunchCmdNormalLaunch
As we have already seen, this code opens the application normally.

sysAppLaunchCmdSystemReset
Sent after a reset occurs.

TIP:   In the 4.0 OS and prior ones, some launch codes are sent to every installed application (for example, after a sync, a reset, or a time change). When a user has many applications installed, this broadcast can be slow. In the future, they may only be sent to those applications that have registered for that particular notification. This registration is available with a Palm OS 3.5 call SysNotifyRegister.

Launch Flags

The launch flags specify important information about how the application is being executed. Here are some examples:

Scenarios

To help clarify the relationship between the application and the times when the system calls it, let's look at examples of when this happens and the flow of code.

Normal launch

Your application gets a launch code of sysAppLaunchCmdNormalLaunch when it's opened, and launch flags of hexadecimal 0x8e, specifying the following, OR-ed together:

sysAppLaunchFlagDataRelocated
This is a private Palm OS flag.

sysAppLaunchFlagUIApp
The application should show a UI.

sysAppLaunchFlagNewGlobals
The application has just been allocated global variables.

sysAppLaunchFlagNewStack
The application has been allocated a new stack.

Find when another application is active

When the Memo Pad is the active application, and the user does a Find, the Find Manager calls the PilotMain of every installed application. When our application is called, the launch code is 1 (sysAppCmdFind), and the launch flag is 0 (no globals, no UI).

Find when your application is active

Things happen differently when we do a Find with our application already open. In this case, PilotMain is still called with the same launch code: sysAppLaunchCmdFind, but now the launch flag is different. It is 0x10 (sysAppLaunchFlagSubCall), specifying that the OReilly Starter application is already open and running. This means that global variables have been allocated and initialized, and we are running as an indirect subroutine call from the application.

Figure 5-4 shows the stack trace when the Find is done in this case. The stack trace shows that PilotMain is called (indirectly) from our AppEventLoop (which itself is called from our original PilotMain).

Figure 5-4. The stack trace when doing a Find from within our own application

 

Examples

There are other communications between the Palm OS and your application that you might want to handle in your code. Particular events, or overall access might be important for you to control. In any case, here are events some applications want to handle:

Let's take a look at the code required to handle each of these instances.

Handling a Pen-Down Event

Normally, you will not handle pen-down events directly, but instead handle higher-level events like ctlSelectEvent. However, occasionally, applications will want to be on the lookout for a penDown event. An example is the Address Book: tapping and releasing on the display of an address in the display view switches to the edit view.

The source code to the Address Book is part of the Palm OS 3.5 SDK. The RecordViewHandleEvent in Address.c from that example contains the following case:

case penDownEvent:
         handled = RecordViewHandlePen(event);
         break;

RecordViewHandlePen handles penDown events in the display-only view of the record (see Example 5-15).

Example 5-15: RecordViewHandlePen in the Address Book source

static Boolean RecordViewHandlePen (EventType * event)
{
   Boolean      handled = false;
   FormPtr      frm;
   RectangleType r;
   Int16        x, y;
   Boolean      penDown;
 
   // If the user taps in the RecordViewDisplay take her to the Edit View.
   frm = FrmGetActiveForm(  );
   FrmGetObjectBounds(frm, FrmGetObjectIndex(frm, RecordViewDisplay), &r);
   if (RctPtInRectangle (event->screenX, event->screenY, &r))
      {
      do 
         {
         PenGetPoint (&x, &y, &penDown);
         } while (penDown);
      
      if (RctPtInRectangle (x, y, &r))
         FrmGotoForm (EditView);
         
      handled = true;
      }
      
   return handled;
}

The calls to the Form Manager routines FrmGetActiveForm and FrmGetObjectBounds yield the rectangle containing the address display area. RctPtInRectangle checks to see whether the pen-tap location (event->screenX and event->screenY) are within those bounds. Note that it then enters a loop calling PenGetPoint until the user releases the stylus. If the user lets go within those same bounds, the code switches to the edit view.

Handling a Graffiti Character

To show you an example of handling a graffiti character, we turned to the Reptoids game (one of the samples in the Palm OS 3.5 SDK). While the game is running, entering a "t" character displays the amount of time spent playing on-screen.

The Rocks.c file has the following check in MainViewHandleEvent:

else if (event->eType == keyDownEvent)
    {
    // Time spent playing.       (Quick code at this point.)
    if (event->data.keyDown.chr == 't')
        {
        // ... Code that displays game-time is here.     
        }
 
    return true;
    }

Note that the routine checks the incoming character by looking within the event: event->data.keyDown.chr.

Overriding Hard-Button Behavior

Some applications want to redirect the hard button presses (the Date Book key, for instance) for their own use. An obvious example would be games. Pressing a hard button generates a keyDownEvent and as such can be looked for by an application. Indeed, the keyDownEvent is sent for all of these:

In all the instances, a special modifier bit (the commandKeyMask) is set in the modifiers associated with that key. This distinguishes it from a normal Graffiti character.

However, for all but the scroll buttons, the system handles the keyDownEvent, and doesn't allow the form's event handler to deal with it.

Now, let's see how we can use this information to modify our source code to our advantage. We will make an application where tapping on the Date Book key doesn't bring up the Date Book, but instead does something app-specific.

First off, we need to avoid calling SysHandleEvent when that key is pressed (see Example 5-16).

Example 5-16: AppEventLoop that doesn't call SysHandleEvent for taps on the Date Book key

static void AppEventLoop(void)
{
  Err error;
  EventType event;
 
  do {
    Boolean isDatebookKey;
    
    EvtGetEvent(&event, evtWaitForever);
    
    isDatebookKey = (event.eType == keyDownEvent)
      && (TxtCharIsHardKey(event.data.keyDown.modifiers, 
        event.data.keyDown.chr))
      && (event.data.keyDown.chr == vchrHard1);
 
    if (isDatebookKey || ! SysHandleEvent(&event))
      if (! MenuHandleEvent(0, &event, &error))
        if (! AppHandleEvent(&event))
          FrmDispatchEvent(&event);
 
  } while (event.eType != appStopEvent);
}

Note that we figure out whether it is a hard key, and then whether it is the Date Book key (vchrHard1). If it is, we don't call SysHandleEvent, and so the normal processing for that character won't happen. However, the event will be passed to FrmDispatchEvent (after being ignored by MenuHandleEvent and AppHandleEvent), and from there to our event handler. Here's code in MainFormHandleEvent that switches to the second form if the Date Book key is pressed:

switch (event->eType) 
  {
  case keyDownEvent:
    if (TxtCharIsHardKey(event->data.keyDown.modifiers, 
        event->data.keyDown.chr)
      && (event->data.keyDown.chr == vchrHard1)) {
      FrmGotoForm(SecondForm);
      handled = true;
    }
    break;

Controlling the Exiting of an Application

Some turnkey applications take over the machine and don't allow any other applications to run. For example, a Palm OS device carried by delivery people might only run a delivery application; it's locked for any other purpose.

TIP:  Admittedly, this is a very rare UI. A standard Palm application should always quit when the user requests another application (by pressing one of the hard buttons, for instance). Standard applications should not have an explicit UI for quit, however. Users implicitly quit an application by starting another.

How do you write an application that takes control of the Palm OS unit and won't let go? It's simple. In order to keep other applications from running, you rudely refuse to exit from your main event loop. As a result, the application never quits (except on a reset). All the OS can do is post an appStopEvent to the event queue, requesting (nay, strongly urging!) your application to quit. It's up to the application itself to actually quit, though.

In the simplest case, you might just code your event loop so that it never exits (see Example 5-17).

Example 5-17: AppEventLoop that never exits

static void AppEventLoop(void)
{
  Err error;
  EventType event;
 
  do {
    EvtGetEvent(&event, evtWaitForever);
    
    if (! SysHandleEvent(&event))
      if (! MenuHandleEvent(0, &event, &error))
        if (! AppHandleEvent(&event))
          FrmDispatchEvent(&event);
 
  } while (true);  // Don't ever quit.
}

This will ensure that your application, once running, will never exit. It'll silently ignore an appStopEvent that is posted to the event queue.

Another scenario is an application that won't quit unless given a specific command. Some turnkey applications provide a mechanism (perhaps a password-protected button or menu item) to quit the application and open up the unit to the rest of the Palm UI. The easiest way to implement this is to have a global variable specifying whether appStopEvents should be ignored. When the button is pressed, the application can post an appStopEvent, and set the variable. Here's how the event loop would change:

static  void AppEventLoop(void)
{
  Err error;
  EventType event;
 
  do {
    EvtGetEvent(&event, evtWaitForever);
    
    if (! SysHandleEvent(&event))
      if (! MenuHandleEvent(0, &event, &error))
        if (! AppHandleEvent(&event))
          FrmDispatchEvent(&event);
 
  } while (!(event.eType == appStopEvent && AppShouldStop(  )));
}

AppShouldStop just returns the value of the global:

Boolean gShouldStop = false;
 
Boolean AppShouldStop(  )
{
  return gShouldStop;
}

Now, when the specific action occurs (like pressing a button or choosing a menu item), a routine MakeAppStop would be called to set the global, and post an appStopEvent to the event queue (see Example 5-18).

Example 5-18: MakeAppStop that causes the application to quit

void MakeAppStop(  )
{
  UInt32 romVersion;
 
  gShouldStop = true;
  FtrGet(sysFtrCreator, sysFtrNumROMVersion, &romVersion);
  if (romVersion < sysMakeROMVersion(2,0,0,sysROMStageRelease,0)) {
    AppLaunchWithCommand(sysFileCDefaultApp, 
      sysAppLaunchCmdNormalLaunch, NULL);
  } else
    PostAppStopEvent(  ); // Launch previous app.
}

WARNING:   Palm OS 1.0 relaunches the last application, so the code must explicitly launch an application in this case. (The default application is the one that is shown after a reset.) Any post-1.0 OS launches the previous application, so the code just posts an appStopEvent.

Here's the code that posts the appStopEvent. Note that whenever you create an event yourself, you should zero out the entire structure so that unused fields are zero:

static void PostAppStopEvent(  )
{
  EventType event;
  
  // Set all unused fields to 0.
  MemSet(&event, sizeof(event), 0);
	
  event.eType = appStopEvent;
  EvtAddEventToQueue(&event);
}

What to Remember

In this chapter, we have given you a description of important terminology, standards, and a description of how an application interacts with the Palm OS on a device. Most importantly, you should remember the following:

From all of this information, you should now be well on your way to understanding this application architecture. In the following chapters, you will be using this information to move beyond our simple OReilly Starter application to create a full-featured application.

Back to: Palm OS Programming, 2nd Edition


oreilly.com Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies | Privacy Policy

© 2001, O'Reilly & Associates, Inc.
webmaster@oreilly.com