PRC-Tools Tutorial
by Andrew Howlett
Last updated March 2001
Copyright © 1997 by Andrew Howlett
portions Copyright © 1997 by Theodore Ts'o
portions Copyright © 1994 by Palm Computing
portions Copyright © 1997 by R. Critchlow and B. Winton
This tutorial is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option)
any later version.
This tutorial is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
A copy of the GNU General Public License is included at the end of
this document. If the license is missing, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.
This tutorial records what I figured out as I taught myself to program
my Palm using GCC and PilRC. Maybe it can help other recreational programmers.
Many thanks to the experts who built free tools for Palm OS Programming.
If you find errors in this tutorial or would like to contribute improvements,
please let me know. For document control purposes, please do not distribute
modified copies of the tutorial.
Some of the information in this tutorial is drawn from PilRC
documentation, Palm Computing documentation, and Palm OS header
files. That information will be identified when presented and is
copyright of the contributor. "Palm Computing" and "Palm OS" are
registered trademarks of Palm Computing. Motorola and DragonBall are
registered trademarks of Motorola. Adobe and Acrobat are registered
trademarks of Adobe.
You can download this tutorial
and all the supporting files, to read it offline and to play with the
code. Or, you can download just the Text2Hex
example code.
Table of Contents
1.0 INTRODUCTION
2.0 PALMOS MEMORY AND DATABASES
3.0 PILOTMAIN
4.0 FORMS
5.0 OTHER RESOURCES
6.0 SIMPLE EXAMPLE
GNU GENERAL PUBLIC LICENSE
This tutorial will explain how to build simple Palm OS applications
using free software tools. This tutorial defines a simple application
as an application with one form, one or two alert dialogs, one menu,
and any number of buttons, fields, bitmaps, and labels. This tutorial
supplements the documentation listed in section 1.5.
Later editions of this tutorial may explain how to build more
sophisticated Palm OS applications. More sophisticated applications use
tables, resource databases, data databases, popup lists, checkboxes,
multiple forms, and other techniques.
This tutorial is written for newbies. Readers should be familiar with
ANSI C.
If you don't know ANSI C but still want to program your Palm then you
have two options:
- Learn ANSI C. Check the web, especially Yahoo's
list of C and C++ courses. Or, check used book stores for cheap
programming books. I picked up an old copy of Mastering Borland
C++ version 3.1 for $5.
- Use one of the other supported
languages: Java, Basic, etc. As this tutorial only covers
programming in ANSI C, you'll have to find another tutorial if you
choose this route.
Be careful! Palm OS itself does not support the Standard C Library.
PRC-Tools adds partial support for the
Standard C Library, but many facilities are still missing.
This manual is limited to Palm OS 3.5. This tutorial was developed on a
GNU-Linux system, however it should be equally applicable to development
with the GNU Palm under a MS Windows environment.
The great thing about programming Palm OS using free tools is that
there is lots of free documentation. You must read the first three
references. The rest are highly recommended. You will require Adobe
Acrobat to read any files with a filename suffix of "pdf". Acrobat
Reader can be downloaded from Adobe's
website.
- Palm OS Reference. This monolithic document is available
from Palm Computing's developer resources webpage,
http://www.palmos.com/dev/.
The document is in one large pdf file, which makes for easy searching.
This document describes the Palm OS application programmer interface
(API) in detail. When this tutorial mentions an API call you should
look it up in the Palm OS Reference.
- Palm OS Companion. The Palm OS Programmer's
Companion is a type of "howto" document describing why
the API works the way it does. The Palm_OS_Reference.pdf
document contains many links to the companion.pdf
document. You need this file. Download it from the Palm Computing tech
site.
- Palm OS Programmer's
FAQ. Read the FAQ. In case you didn't hear that,
Just do it.
- Using
PRC-Tools. You should read the prc-tools manual, especially
if you decide to create complicated projects using multiple
source files.
- GNU documentation.
Documentation for GCC, MAKE, GDB, etc. This covers the portable
aspects of these tools; the previous item covers the Palm
OS-specific aspects.
- PilRC
User Manual.
- Palm OS Knowledge Base.
This is Palm Computing's searchable database of many useful articles.
- News.falch.net. The pilot.programmer.gcc newsgroup
on the news.falch.net news server contains many useful prc-tools
related articles.
- Palm-dev
archive. Palm Computing has it's own news server for Palm OS
topics. To post to this news service you must be a registered
Palm OS developer. However, anyone can read and search the
palm-dev archive.
If you ask a question that is already in one
of the above mentioned FREE documents, you probably won't get an
answer.
This tutorial and the sample application were developed using the
following tools:
|
Tool |
Version |
|
PRC-Tools |
Prc-tools-2.0.90
binutils-2.9.1
gcc-2.95.2
gdb-4.18 |
|
Palm OS SDK |
SDK 3.5 |
|
Resource Compiler |
PilRC v2.7 |
|
Emulator |
POSE 3.0a8 |
|
Documentation |
Palm_OS_Reference.pdf, SDK 3.5
Palm_OS_Companion.pdf, SDK 3.5
Adobe Acrobat Reader
pilrc v2.7 manual
prc-tools v2.0 manual |
Make sure that you
have the right version of PilRC. Sometimes the windows palm-GCC package
installs an old version of PilRC. Get the latest version of PilRC from
www.ardiri.com.
There already exists excellent instructions on how to
install the prc-tools software. If you plan to install
prc-tools in the MS Windows environment, you should read the
instructions written by Aaron Ardiri. If you plan to install
on a unix or GNU/linux system, then follow the directions at
http://lisas.de/~david/palmos/.
This tutorial will start by explaining Palm OS databases. To new Palm
OS programmers, this may seem a little strange. Most C programming
books start by talking about the user interface and coding, then
explain how to build databases. But the Palm OS system is a little
bit different than your desktop PC operating system. So throw away your
preconceptions regarding hard drives and RAM and learn how Palm OS stores
its applications.
The Motorola DragonBall CPU can address up to 4GB of memory. No Palm
OS device is currently equipped with 4GB, so most of this address space
is not implemented.
Your Palm's RAM is divided into two areas: dynamic RAM and storage
RAM. Dynamic RAM is the memory available to an application as it runs,
similar to the RAM installed in your desktop computer. Dynamic RAM is
used for the current application's global variables, local variables,
stack space, etc. Depending on the OS vesion and the amount of memory
in the device, the amount of available dynamic RAM varies from about 12
KB to a bit under 200 KB. (See the Memory article in
Palm's knowledge base for details.) To be safe, you should only expect
to be able to access about 32 KB.
32 KB isn't very much. Consider a problem I had porting a game to
Palm OS: the game board is composed of a rectilinear grid of tiles, like
a checkerboard but 100 tiles across and 80 tiles up and down. Each tile
is described by a 50 byte data structure. Normally this board would be
defined by a statement like:
struct tile_type game_board[100][80];
This global definition requires 100 x 80 x 50 = 400,000 bytes of
dynamic memory. Oops. Not enough dynamic RAM. This means that I had to
implement the gameboard in storage RAM as a database.
Storage RAM is the RAM used to store applications and their data,
similar to the hard drive in your desktop computer. This analogy is handy
but remember that the comparison isn't identical. For instance, when you
launch a desktop application on your PC, the code is transferred from
hard disk to dynamic RAM. But the Palm OS device works differently. Palm
OS doesn't copy the application code to dynamic RAM, it just moves the
program counter to point at the code where it sits in storage memory.
Everything in storage RAM is formatted using one of two Palm OS
database formats: "normal" database format and resource database format.
A normal database consists of a database header and from zero to
dmMaxRecordIndex records
of variable length. Each record is a chunk of memory, and SDK 3.5 has
a limit of 65,536 bytes in a chunk, so a record has a maximum length
of a little less than 65,536 bytes. (You have to subtract a few bytes
for the record header). A normal database is very similar to a variable
record length random access file which might be implemented on a desktop
computer, except for a few minor details. One important detail is that
you can get a pointer to the record data and use it like you would any
other memory pointer. For instance, if your database contains a 50 byte
record used to store addresses, you don't have to transfer the address
information to and from dynamic RAM. You can get a pointer directly to the
address data in storage memory and use that pointer in API calls such as
WinDrawChars. This technique conserves power and dynamic memory.
Resource databases are similar to normal databases except that that the
dmHdrAttrResDB bit is set in the attributes field of the database header,
which means that each record of the database stores a Palm OS resource.
A list of Palm OS resources is contained in Palm_OS_Reference.pdf. One
example of a resource database is a PRC file. An application consists
of one or more executable code resources and many user interface (UI)
resources bundled into a resource database. You can actually open other
applications as resource databases and use their icons or bitmaps. I
once used a resource database to store a catalog of 536 bitmap icons.
Palm OS identifies different databases by various database
header fields, the most important fields being "type" and
"creatorID". CreatorID is sometimes misnamed application ID. Type and
creatorID are 32 bit values. In C they can be stored as unsigned
long integers, but they are usually represented by four ASCII
characters (you might find some applications using hexadecimal or
decimal representation for #DEFINE instructions). For instance,
Palm OS applications are database of type 'appl' (1634758764
or 0x6170706c). If you want to be sneaky, you could create an
application using a type other than 'appl', then it won't show up in the
"Applications" browser. Of course, it would be difficult to launch an
application that doesn't show up in the Applications browser (but not
impossible!). There exists a very interesting Palm OS application named Insider
that will list the databases installed on your Palm OS device and let
you examine their database header fields.
Some other types are commonly used. The built-in applications
use 'DATA' type for their data databases, such as your addresses and
appointments. The 'HACK' type identifies hackmaster extensions. A good
rule of thumb is to use 'Rsrc' type to identify resource databases and
'Data' type to identify data databases. Of course, type and creatorID are
case sensitive because 'A' is ASCII 0x40 whereas 'a' is ASCII 0x60. All
lower case creatorIDs are reserved for use by Palm Computing, so your
creatorID must have at least one upper case letter.
Because every application must have a unique type/creatorID
combination, and since all applications have type 'appl', your
application's creatorID must be different from all other creatorIDs
in the Palm OS universe. If you load an application into your
Palm device which has the same creatorID as an application
that is already in storage memory, then the pre-existing
application will be erased. To avoid creatorID conflicts,
Palm Computing has a registration process for creatorIDs at
http://www.palmos.com/dev/tech/palmos/creatorid/. There is
no cost to register an ID, but you have to provide some professional
information. If you're just fooling around, you don't need to register
your creatorID, but if you plan to distribute your application to other
Palm OS users, then you must register the creatorID with Palm Computing.
In ANSI C, a short integer is at least 16 bits wide, a long integer
is at least 32 bits wide, and a plain "int" is ambiguously defined as
at least as big as a short integer and less than or equal to the size
of a long integer.
For Palm OS programming, it is very important to know the exact sizes
of the storage elements. This is partly because you need to pay closer
attention to your storage requirements on a Palm than when programming
for a larger computer, and partly because the Palm database mechanism
is based on raw data records, so you must use fixed-size data types: a
future Palm OS device might have a different size for "int", but "UInt16"
will always be 16 bits. Therefore Palm OS SDK 3.5 defines the following,
unambiguous simple data types:
|
Data type name |
Description |
|
Int8 |
Signed 8-bit value |
|
UInt8 |
Unsigned 8-bit value |
|
Int16 |
Signed 16-bit value |
|
UInt16 |
Unsigned 16-bit value |
|
Int32 |
Signed 32-bit value |
|
UInt32 |
Unsigned 32-bit value |
|
MemHandle |
A handle to a memory chunk |
|
MemPtr |
A pointer to memory |
|
FlpFloat |
32-bit floating point value |
|
FlpDouble |
64-bit floating point value |
|
Boolean |
Takes values 0 and 1 |
The first Palm OS device was called a "Pilot". Thus the entry point for
a Palm OS application is called "PilotMain". PilotMain returns zero if no
errors occur, otherwise it returns the error code. Its prototype is:
UInt32 PilotMain (UInt16 launchCode, MemPtr cmdPBP, UInt16 launchFlags)
There are four parts to a typical PilotMain:
- Decide what launch code was given
- If it's a normal launch code, start application
- Event loop
- Stop application
Below is a generic PilotMain procedure. The start application, event
loop, and stop application code is modularized in separate procedures.
This chapter will explain the launch code test and each procedure.
UInt32 PilotMain (UInt16 launchCode, MemPtr cmdPBP, UInt16 launchFlags)
{
if (launchCode == sysAppLaunchCmdNormalLaunch) // 1. Launch code test
{
UInt16 error = StartApplication(); // 2. Application start code
if (error)
{
return error;
}
EventLoop(); // 3. Event loop
StopApplication(); // 4. Application stop code
}
return 0;
}
There are several reasons why Palm OS might launch your application.
The most obvious reason is that the user tapped on your application's icon
in the Launcher. But there are other reasons to start your application:
for instance, when the user taps on the silk-screen "Find" button,
Palm OS starts each application so that the application can execute
its own find algorithm. Another example happens after the user
synchronizes the Palm OS device: Palm OS starts each application so that
the application can examine any new data in its database(s) and take
any necessary action (e.g. set alarms). So, when Palm OS launches your
application, the first argument it passes is a 16-bit unsigned integer
describing why the system launched your application. These UInt16 values
are called SysAppLaunch commands. You can find a list of all the
commands in the SDK file include/Core/System/SystemMgr.h.
Your PilotMain procedure must check which command Palm OS issued to
your application. If you fail to check which command was issued YOU WILL GET ERRORS. A common newbie mistake is to
forget to test the launch code, then post dumb questions on the newsgroup
asking why their code doesn't work. Simple applications only respond to
the normal launch command, so you should test for sysAppLaunchNormalLaunch
then PilotMain should execute your application code within the if block.
If you look up sysAppLaunchCmdNormalLaunch in the SDK, you'll see that
it's defined as 0. Some programmers therefore use the test expression
if (!launchCode) as a shortcut to test for a normal launch. I
recommend you use the full expression since the full notation is easier
to understand: I've seen at least one post on pilot.programmer.gcc from
someone who was confused by the (!launchCode) notation. Besides, if you
turn on the compiler's optimization, the two notations should compile
to the same machine code anyway, so (!launchCode) really doesn't save
you anything.
Only declarations and the final return statement should be outside
of the command test. PRC-Tools users have reported strange behaviour,
such as the Palm device hanging during hotsync, when even innocent
looking code is added before the command test.
The application start code typically has three functions: open the
application's data database (if any), load system preferences (if any),
and set the first form.
static UInt16 StartApplication(void)
{
UInt16 error;
error = OpenDatabase();
if (error) return error;
FrmGotoForm(formID_StockMarket);
}
If the application uses a database, then the application should open it
before doing anything else. I use a boolean procedure called OpenDatabase
to open my database and create the database if no database exists.
Here's an example of such a procedure:
static Boolean OpenDatabase(void)
{
UInt16 index = 0;
MemHand RecHandle;
MemPtr RecPointer;
StockMarketDB = DmOpenDatabaseByTypeCreator(StockMarketDBType,
StockMarketAppID, dmModeReadWrite);
// if StockMarketDB doesn't exist, create it
// and create a zero record with new game state information
if (!StockMarketDB) {
if (DmCreateDatabase(0, StockMarketDBName, StockMarketAppID,
StockMarketDBType, false))
return 1;
StockMarketDB = DmOpenDatabaseByTypeCreator(StockMarketDBType,
StockMarketAppID, dmModeReadWrite);
RecHandle = DmNewRecord(StockMarketDB, &index, 42);
RecPointer = MemHandleLock(RecHandle);
DmWrite(RecPointer, 0, &SM_Values, 42);
MemPtrUnlock(RecPointer);
DmReleaseRecord(StockMarketDB, index, true);
}
// load game state information
RecHandle = DmQueryRecord(StockMarketDB, index);
RecPointer = MemHandleLock(RecHandle);
MemMove(&SM_Values, RecPointer, 42);
MemPtrUnlock(RecPointer);
return 0;
}
What does this code do? First it tries to open the database which has
type StockMarketDBType and CreatorID StockMarketAppID in read/write mode.
If the database doesn't exist, it creates the database and creates record
zero of the database. Now that we know a database exists (because if it
didn't, we just created it!) we can copy record zero of the database to
a data structure.
Another thing you may want to do in your OpenDatabase procedure,
which isn't shown in the example above, is check the version of the
database. When you hotsync a new version of an application to your
handheld, the previous version of the application is erased. But although
hotsync erases the previous "appl" type, the previous "Data" type data
database is still there. In many cases using the old version of the data
database with the new version of the application causes errors. This is
why new versions of Palm software sometimes come with a warning to delete
previous version before installing. When you delete an application,
every database with that creatorID is deleted, even if the database has
a different type. More sophisticated applications will check the version
of their data database when they run. If the data database is an old
version then a sophisticated application will either delete the old one
and create a new one, or upgrade the old data database to the new format.
System preferences are typically used for restoring the state of the
application to whatever it was when the user last closed the application.
For instance, in a chess game the board configuration might be stored in
system preferences. In the memopad application system preferences records
which category was last open and which memo was open, if a memo was open.
There's no defined limit on the size of your system preferences record
I would suspect that the limit is a little less than 64k bytes,
same as most memory chunks. However, I don't use the system preferences
API calls anymore, so I won't discuss syspref in this article. Instead,
I will mention that I use record zero of my data database in the same
way that I used to use the system preferences API. I usually load the
state information in my OpenDatabase procedure, as can be seen in the
example code above.
After opening and checking the database, the next thing to do is set
the form. Although there are some optimizations which can be applied
to applications which use only one form, I suggest that you use the
generalized method, the API call FrmGotoForm as seen in the last line
of the StartApplication example.
Palm OS applications are event driven. This means that the user, the
operating system, and the application generate operating system events
which are placed in the application's event queue. You can find a full
list of events in the SDK file include/Core/UI/Event.h.
One at a time, the application takes events from the event queue and
handles them, in first-in-first-out order. Event handling is the core
activity of most Palm OS applications: get an event from the queue,
handle it if it's one the application is interested in, and if not,
pass it to the operating system's default event handlers. Continue until
done. :)
The diagram below shows program flow for the event loop. As you can
see, it's very simple. The application gets an event from the event queues
using EvtGetEvent and handles the event. If the event is an AppStopEvent
then execution exits the event loop, otherwise execution loops back to
EvtGetEvent.
You use the EvtGetEvent function to fetch an event from the queue. The
EvtGetEvent function takes two arguments: the first is a pointer to
an EventType structure. EvtGetEvent will load the next event into the
structure. The other argument is a timeout specifying how long to wait
for an event, if there are no events already waiting in the queue. The
timeout is measured in ticks to find out the duration of a tick
on your device, use the SysTicksPerSecond API call.
The most common timeout argument is probably evtWaitForever. This puts
the CPU into doze mode until the user provides some input. Occasionally
the programmer defines a time limit, for instance if the application
is performing an animation which needs to be updated at regular
intervals. Here are some examples of valid EvtGetEvent calls:
EvtGetEvent(&event, 200); // Wait 200 ticks (~2 seconds) for an event.
// If timeout expires, nilEvent is returned.
EvtGetEvent(&event, // Better way of waiting 2 seconds for an event.
2 * SysGetTicksPerSecond());
EvtGetEvent(&event, evtWaitForever); // Self-explanatory
In the first example, if no events happen within 2 seconds then
EvtGetEvent will return with a nilEvent in the event structure. Setting
the time-out value can be used as a simple method of timing for some
applications. However this technique doesn't work for games/applications
that use a lot of user input, because the timer gets reset every time
an event occurs. For instance, it wouldn't work in Space Invaders,
because every time the player pressed a button to move or fire,
EvtGetEvent would exit with a keyDownEvent and the 2 second limit might
never be reached. Setting a time-out on EvtGetEvent is good for timing
simple games, but for more demanding games you'll have to use a more
sophisticated mechanism.
The event handler part of the loop is a little more complicated. Before
your application gets a chance to handle the event, various parts of
the operating system should be offered the event. If the operating
system handles the event, then you shouldn't need to. For instance,
if a penDownEvent occurs within the bounds of a button then you
probably don't want to interfere. After all, you don't want to know
when the button was touched, what you want to know is when it was
released. Usually the next event generated after a penDownEvent is a
penUpEvent. The SysHandleEvent function will determine if the penUpEvent
and penDownEvent occured within the bounds of the same button, and if so
generate a ctlEnterEvent, which indicates that the button was pushed.
Then your application handles the ctlSelectEvent. Simple!
Well, not so simple you have to know what events to look for
for each of the user interface methods (form buttons, grafiti input,
silkscreen buttons, etc.) that you enable for user input. Writing a
Palm OS application is all about carefully selecting which events you
will handle.
Be warned: If you don't call SysHandleEvent or if you carelessly
interfere with the processing of hardware events you can seriously screw
up the behaviour of your Palm OS device.
My EventLoop example, below, lets the system handle most events.
Notice that you give several different parts of the Palm OS a chance
to handle an event: First, SysHandleEvent, second MenuHandleEvent,
and finally FrmDispatchEvent.
There's only one event we care about in this example: frmOpenEvent. We
handle this event because each form may have its own way of handling
events - in other words, its own form event handler function. The last
"generic" handler, FrmDispatchEvent, looks up the form's event handler
and passes the event on to it. But what do you do when the application
changes forms? The form event handler must be changed before control gets
to the FrmDispatchEvent function call. A change of form is indicated by
frmLoadEvent. So in my EventLoop I detect frmLoadEvents before calling
FrmDispatchEvent
Some Palm programmers write a "ApplicationEventHandler" procedure and
call this procedure between MenuHandleEvent and FrmDispatchEvent. The
ApplicationEventHandler processes frmLoadEvent and appStopEvent.
After all these other handlers get their chance, then your application
has an opportunity to handle the event in your customized way. The event
gets passed to FrmHandleEvent. FrmHandleEvent will do some stuff with
the event (I don't know exactly what) then send the event to the form
event handler which you have already defined using FrmSetEventHandler.
static void EventLoop(void)
{
do
{
EvtGetEvent(&event, 200);
if (SysHandleEvent(&event)) continue;
if (MenuHandleEvent((void *)0, &event, &err))
continue;
if (event.eType == frmLoadEvent)
{
formID = event.data.frmLoad.formID;
form=FrmInitForm(formID);
FrmSetActiveForm(form);
switch (formID)
{
case formID_Buy:
FrmSetEventHandler(form, BuyEventHandler);
break;
case formID_Sell:
FrmSetEventHandler(form, SellEventHandler);
break;
case formID_StockMarket:
FrmSetEventHandler(form, StockMarketEventHandler);
break;
case formID_About:
FrmSetEventHandler(form, AboutEventHandler);
break;
case formID_Help:
FrmSetEventHandler(form, HelpEventHandler);
break;
}
}
FrmDispatchEvent(&event);
} while(event.eType != appStopEvent)
}
Application stop code should save the application's state and
close any databases that your application may have opened. Both of
these tasks are optional. Your application may not need to save any
state information. The M+ key in the built-in calculator is a very
simple example of state information. If someone stores a number in the
calculator's memory register, it should still be there next time they
launch the application. So when the application ends, Calculator has to
store it's memory register. It probably uses SysPrefs to do it, but as I
mentioned earlier your applications should use a data database. The second
function of application stop code is to shut down databases. If your
application shuts down and leaves databases open, Palm OS will close them
automatically. However I prefer to explicitly close any databases I may
have opened. I usually have use a separate CloseDatabase procedure. You
may choose to reduce the final PRC size by omitting this call.
Before closing this chapter, you should understand that there are
many variations on PilotMain. As I mentioned, some programmers write
an ApplicationEventHandler. Some programmers write a MenuEventHandler
(MenuEventHandler is always recommended for large apps) and call it
directly from their event loop procedure. You should write some code
and collect samples from different authors to find out what works best
for you.
Humans interact with the Palm OS graphical user interface via user
interface objects. From Palm_OS_Companion.pdf, "A Palm OS UI object is
a C structure that is linked with one or more items on the screen." Palm
OS 3.5 supports the following UI objects:
In addition to these UI objects, Palm OS also supports text and string
processing, and bitmap display.
Palm OS organizes UI objects using "forms". A form
is a set of UI objects grouped together to achieve some common purpose.
For instance, if you press the MemoPad hardware button a form appears on
the screen. The form contains a title object "Memo List" in the upper left
corner, a popup trigger in the upper right corner (categories), a button
in the lower left corner "New", and a two column, eleven row table in the
middle of the screen. These UI objects achieve the common objective of
listing memos of the category selected with the popup trigger. UI objects
are not resources, but they might be associated with a resource, which
provides the UI object with data. For instance,
"Windows" are related to forms, but a little different. A window is
just a section of the screen. For instance, all forms include a window
where the UI objects are displayed. But not all windows are forms. For
instance, the screen space occupied by a pull down menu is a window. The
screen space occupied by an alert box is a window. Palm OS also permits
off-screen windows. Just imagine an invisible section of screen on the
back of your handheld that follows all the same rules as normal windows. If
you want to draw on the LCD screen inside your window, use the WinDraw
API calls, such as WinDrawChars, WinDrawBitmap, etc. However stuff you
draw using WinDraw calls isn't part of the form, so it will be erased by
a FrmDrawForm call.
A menu might be associated with the form using the MENUID identifier.
The form can be specified as modal or non-modal. "Modal forms ignore pen
events outside their boundaries" (Palm_OS_Reference.pdf). Helpstrings are
only usable with modal forms. A modal form displays the information icon
on the right side of the title bar. If a helpstring is defined, then the
helpstring text is displayed in an information alert box when the user
taps the title bar information icon. You can define a default button for
the form. If the form quits, then the system will add a ctlSelectEvent
to the Event queue with buttonID equal to the button ID set by DEFAULTBTNID.
For instance, suppose your form has two buttons, "do it" and "cancel".
This form is displayed when the application stops due to some other user
input, for instance another application was launched from the Applications
screen. You may want the "cancel" button to execute automatically to release
locked handles, reset global variables, etc. This is a situation where
you could use DEFAULTBTNID.
To build forms and other resources, Wes Cherry created the Palm
Resource Compiler, "PilRC". PilRC has been upgraded and maintained
by Aaron Ardiri for the last few years. Defining a form requires
multiple PilRC instructions because every option and UI object must be
specified. PilRC uses the FORM identifier to identify the start of a
form definition, followed by the form options, then the UI objects are
contained between BEGIN and END identifiers. I indent the UI object
definitions by four spaces, but this is not required. Order of the UI
objects is not important. This chapter will discuss the most commonly
used UI objects:
PilRC resources start with an identifier, followed by a number of
fields. Optional fields are enclosed by [ ], required fields are enclosed
by < >. A full explanation of all resource types is in the PilRC's
User Manual.
TITLE <Title.s>
Title gives your form a title. The title appears at the top of the form
as white characters on black background. The characters are always font
1. (The fonts are listed in figure 4.2.2. Download the ASCIIChart tool
to see the different fonts on your Palm.) For modal forms the title is
centered. In a non-modal form, the title bar uses the top fifteen pixel
rows of the form. In a non-modal form the title bar uses only fourteen
pixel rows.
LABEL <Label.s> ID <Id.n> AT (<Left.p> <Top.p>) [USABLE] [NONUSABLE]
[FONT <FontId.n>]
The label object allows you to fix text strings on the form. The
font and location is user selectable. (See below for a list of font
IDs.) Usable and nonusable supposedly allows the user to determine whether
or not the Label is drawn. I suggest you set all labels USABLE. If
you want text to appear and disappear use the Field UI object or the
WinDrawChars API call.
|
FONT ID |
Description |
Height |
Width |
Comments |
|
0 |
Standard |
11 |
variable |
extended ASCII character set |
|
1 |
Bold |
11 |
variable |
extended ASCII character set |
|
2 |
Large |
14 |
variable |
extended ASCII character set |
|
3 |
Symbol |
10 |
variable |
alarm clock, up and down arrows, memo, bullet,
etc. |
|
4 |
Symbol11 |
11 |
variable |
AKA checkbox font |
|
5 |
Symbol7 |
8 |
11 |
up and down triangles |
|
6 |
LED |
19 |
variable |
calculator numbers |
BUTTON <Label.s> ID <Id.n>
AT (<Left.p> <Top.p> <Width.p> <Height.p>)
[USABLE] [NONUSABLE] [DISABLED] [LEFTANCHOR] [RIGHTANCHOR] [FRAME]
[NOFRAME] [BOLDFRAME] [FONT <FontId.n>] [GRAPHICAL]
[BITMAPID <bitmapID.n>] [SELECTEDBITMAPID <bitmapID.n>]
A button is exactly what it sounds like: an area on screen, usually
framed, which activates some process when touched. You have the option
of labeling the button, you decide where the button is located, you decide
whether it's usable or non usable. For instance, if your application is
a list of words, and you flip from word to word with "Previous" and "Next"
buttons, then you may want to make the "Previous" button unusable on the
first word and the "Next" button unusable on the last word. Initially you
can set the object usable using it's PilRC definition. During runtime,
you can set it usable and nonusable using the FrmSetObjectUsable and FrmSetObjectNonusable
calls. Setting a button non-usable removes it from the screen. Buttons
cannot be "grayed out" as done in some other operating systems. However,
some programmers cheat by making a bitmap that looks like a "grayed out"
button, and then calling WinDrawBitmap after FrmSetObjectNonusable.
When you tap a button SysHandleEvent puts a ctlSelectEvent onto the
queue. The event.data.ctlEnter.controlID field contains the resource ID
of the button that was tapped.
FIELD ID <Id.n> AT (<Left.p> <Top.p> <Width.p> <Height.p>)
[USABLE] [NONUSABLE] [DISABLED] [LEFTALIGN] [RIGHTALIGN]
[FONT <FontId.n>] [EDITABLE] [NONEDITABLE] [UNDERLINED] [SINGLELINE]
[MULTIPLELINES] [MAXCHARS <MaxChars.n>] [DYNAMICSIZE]
Fields are probably the most powerful tool in the Palm OS UI. You
can use a field to display a string. Unlike LABEL, however, you can
change the string at runtime using FldSetText, FldSetTextHandle,
or FldSetTextPointer. FldSetText can only be used with NONEDITABLE
fields. That leads into the important thing about fields - the user can
be allowed to edit the field. Using a field the user can write memos,
record names and addresses, add to do list items. Lots of applications
use fields. The great thing is that Palm OS handles all the messy text
processing stuff like backspace, changing focus, where's the cursor,
selecting text, etc. Your application just deals with the final result.
It's best to use FldSetTextHandle when setting the text for editable
fields. When you use handles the memory manager can resize the allocated
memory. As far as I can tell, if you use FldSetTextPointer then the
allocated memory cannot change. When you work with handles, if the user
only enters one character, memory manager only allocates one byte. If the
user enters ten thousand characters, then memory manager allocates ten
thousand bytes. Figure 4.5.2 shows my WordPower app using handles with
fields and a data database. Each database record stores a text string. To
edit the database records, all you have to do is get the handle of the
record and assign it to a field. Palm OS will use the database record as
it's field text and will resize the database record as required. This is
cool. And it saves Palm programmers time and RAM. But remember to set
the field handle to NULL before shutting down the form, or else you'll
get an error.
static void EditGetRecord(void)
{
MemHandle recHandle;
// Set handle for Word field to Word database
recHandle = (Handle) DmGetRecord(WordDB, CurrentRecord);
FldSetTextHandle(fieldptr_Word, recHandle);
// Set handle for Definition field to Definition database
recHandle = (Handle) DmGetRecord(DefinitionDB, CurrentRecord);
FldSetTextHandle(fieldptr_Definition, recHandle);
}
You can make a field UNDERLINED or NONUNDERLINED. For debugging,
I find it best to make all fields underlined, that way I can see where
they are. You can limit the field to one line (SINGLELINE) or you
can permit multiple lines (MULTIPLELINES). For multiple line fields
remember to use the FldRecalculateField call after setting the text to
recalculate the word wrapping parameters. Your application can change
the field options during run-time using the FldSetAttributes call.
FORMBITMAP AT (<Left.p> <Top.p>) BITMAP <BitmapId.n> [NONUSABLE]
A bitmap is actually a separate resource, defined outside a form. But
to mount a bitmap inside a form, you must use the FORMBITMAP object. For
instance:
FORMBITMAP AT ( 20 20 ) BITMAP 1000
puts bitmap resource 1000 on the form with the top left corner at
(20, 20). Only use the FORMBITMAP object when you want the BITMAP to
be fixed on the screen at the same location visible all the time. For
instance, if you want a fancy frame around a certain form, you could add
the frame to your application as a bitmap resource and lock it into your
form like this:
FORMBITMAP AT ( 0 0 ) BITMAP bitmapID_FancyFrame
If, on the other hand, you want the bitmap to appear and disappear,
to be changed with other bitmaps, to move around the screen don't use
FORMBITMAP. Instead use the technique described in section 5.4.
GRAFFITISTATEINDICATOR AT (<Left.p> <Top.p>)
When you edit a memo, the graffiti state indicator is the symbol in the
bottom right corner which shows special symbols for shift, shift lock,
and punctuation. The graffiti state indicator isn't automatically there
you have to add it to your form resource, with code like the
above PilRC statement. You don't have to do anything special to manage
the indicator: Palm OS keeps track of shift keystrokes and will make
sure the graffiti state indicator shows the appropriate symbol.
In Palm OS, the form and each UI object exist as data structures. Each
data structure is linked with the object on the screen, so if you change
the value of the "pos.x" element of a FormLabelType structure, then the
label will be in a different horizontal position the next time the form
is drawn. The structures for resources and UI objects are contained in the
Palm header files, most of which are in the SDK directory include/Core/UI.
The form declaration in PilRC will put objects on the screen, but
if you want anything to happen you have to write a form event handler
a C function. Section 3.4 mentioned that the event loop calls
FrmEventHandler and FrmEventHandler calls your customized form event
handler. Section 3.4 also demonstrated how you inform Palm OS of your
custom form event handler using the FrmSetEventHandler call.
My convention is to give the form event handler the same name as the
form, however many programmers add the words "EventHandler" to the end of
all their event handler procedure names, which is probably a good idea.
You could even put "FormEventHandler" at the end of your procedure name,
if you want to make it really obvious. The form event handler deals with
events relevant to your form. If your form has a button, then your form
event handler has to detect ctlEnterEvent. If your form has a table, your
form event handler will have to detect and process a tblSelectEvent. If
your form has many objects, then the form event handler can be quite long.
To reduce the procedure's size and improve it's readability, typically
the form event handler will call other procedures which performs the
activities associated with the detected events. Here's my form event
handler for the main form of my Stock Market game:
Boolean StockMarketEventHandler(EventPtr event)
{
UInt16 handled;
switch (event->eType) {
case frmOpenEvent:
StockMarketDrawForm();
break;
case ctlSelectEvent:
// A control button was pressed and released.
if (event->data.ctlEnter.controlID == buttonID_Next) {
RollDice();
SM_Values.turn++;
StockMarketDrawForm();
handled = true;
}
if (event->data.ctlEnter.controlID == buttonID_Buy) {
FrmGotoForm(formID_Buy);
handled = true;
}
if (event->data.ctlEnter.controlID == buttonID_Sell) {
FrmGotoForm(formID_Sell);
handled = true;
}
break;
case nilEvent:
RollDice();
SM_Values.turn++;
StockMarketDrawForm();
handled = true;
break;
default:
return 0;
}
return handled;
}
Chapter four discussed the form resource, but your application will
require other resources in order to work. This chapter will explain the
following resources:
VERSION ID <VersionResourceId.n> <Version.s>
Optional. You can use this to indicate the version of the application
or resource database. The resource is a string, so you can include characters
as well as numbers.
STRING ID <StringResourceId.n> <String.ss>
You can define strings in the global section of your code, or you
can define them as tSTR resources. Modal forms can be associated with
a string resource for their help or "tips" button. If you code constant
global strings as string resources, then they can be accessed by other
applications. Instead of using a data database to save memos, you could
use a resource database and save each memo as a string resource. I
haven't tried this yet, so no sample code.
ALERT ID <AlertResrouceId.n>
[HELPID <HelpId.n>]
[INFORMATION] [CONFIRMATION] [WARNING] [ERROR]
BEGIN
TITLE <Title.s>
MESSAGE <Message.ss>
BUTTONS <Button.s> <BUTTON.s>...
END
There are four different types of Alert resources: information,
confirmation, warning and error. The only difference between the four
is the bitmap displayed when it pops up. Alert resources are handy
for displaying a message and halting the application until the user
acknowledges the message. The alert resource is modal, meaning that it
stays on the screen until the button is pushed. For this reason, your
Alert IDs should almost always have at least one button.
You can use an alert for user input. You don't need to write an event
handler for the alert's buttons. Instead, the FrmAlert and FrmCustomAlert
calls return the number of the button that the user pushed. The buttons
are numbered in the order that they are declared in the PilRC file. When
you call FrmAlert the alert box is displayed. Your application halts
until the user presses one of the buttons, then the number of the button
is returned to your application.
Most alert IDs are constant, meaning that you define what they look
like in the PilRC file and they always look the same. But you can also
make dynamic alert boxes. Use the FrmCustomAlert call. When you call
FrmCustomAlert you pass up to three strings. Palm OS looks for the symbols
"^1" "^2" and "^3" in the MESSAGE part of the requested alert box, and
replaces these symbols with the strings defined in the FrmCustomAlert
call. This is a fairly powerful capability. For example:
// PilRC file
ALERT ID 1000
INFORMATION
BEGIN
TITLE "Custom Alert Demo"
MESSAGE "First choice is ^1 \n" \
"Second choice is ^2 \n" \
"Third choice is ^3"
BUTTONS "First" "Second" "Third"
END
// GCC file
switch FrmCustomAlert(1000, "Apples", "Oranges", "Bananas");
{
case 0:
WinDrawChars("Apples", 6, 30, 30);
break;
case 1:
WinDrawChars("Oranges", 7, 30, 30);
break;
case 2:
WinDrawChars("Bananas", 7, 30, 30);
break;
}
BITMAP [<ResType.s>] ID <BitmapResourceId.n> <BitmapFileName.s>
[NOCOMPRESS] [COMPRESS] [FORCECOMPRESS]
BITMAPGREY [<ResType.s>] ID <BitmapResourceId.n> <BitmapFileName.s>
[NOCOMPRESS] [COMPRESS] [FORCECOMPRESS]
BITMAPGREY16 [<ResType.s>] ID <BitmapResourceId.n> <BitmapFileName.s>
[NOCOMPRESS] [COMPRESS] [FORCECOMPRESS]
BITMAPCOLOR16 [<ResType.s>] ID <BitmapResourceId.n> <BitmapFileName.s>
[NOCOMPRESS] [COMPRESS] [FORCECOMPRESS]
BITMAPCOLOR [<ResType.s>] ID <BitmapResourceId.n> <BitmapFileName.s>
[NOCOLORTABLE] [COLORTABLE]
[TRANSPARENT r g b] [TRANSPARENTINDEX index]
[NOCOMPRESS] [COMPRESS] [FORCECOMPRESS]
BITMAPFAMILY [<ResType.s>] ID <BitmapResourceId.n>
<BitmapFileName.s> ... <BitmapFileName.s>
[NOCOLORTABLE] [COLORTABLE]
[TRANSPARENT r g b] [TRANSPARENTINDEX index]
[NOCOMPRESS] [COMPRESS] [FORCECOMPRESS]
BITMAPFAMILYSPECIAL [<ResType.s>] ID <BitmapResourceId.n>
<BitmapFileName.s> ... <BitmapFileName.s>
[NOCOLORTABLE] [COLORTABLE]
[TRANSPARENT r g b] [TRANSPARENTINDEX index]
[NOCOMPRESS] [COMPRESS] [FORCECOMPRESS]
Palm OS 3.5 has powerful bitmap capabilities in support of the color
series of Palm OS handhelds. Palm OS supports monochrome bitmaps (BITMAP
pilrc type), 2-bit per pixel (2 bpp) grey bitmaps (BITMAPGREY), 4 bpp
grey bitmaps (BITMAPGREY16), 4 bpp color (BITMAPCOLOR16), and 8 bpp color
(BITMAPCOLOR). The color bitmap resources use indexed color tables,
so 4 bpp means that the color table contains 16 colors. Likewise the 8
bpp color bitmap uses a 256 index color table. The color table consists
of the usual RGB quad structures. The index color bitmaps also support
a transparent index. If no color table is included with the bitmap,
then the OS will use the draw window's current palette. Usually this
is the default system palette, but you can change the current palette
using the WinPalette function. This can be handy if you need to draw
many bitmaps which have a common palette.
PilRC 2.7 has a quirk when creating color bitmaps. If you use the
COLORTABLE option, then pilrc will create a bitmap resource using
the with a colortable and preserving the indices in your original
bitmap. Colortables use up some memory, but that isn't a big deal anymore
with 8MB handhelds. However, Palm OS draws colortables very slowly.
Palm OS recommends that programmers use the WinPalette function to
change the draw window's palette to your desired colortable, then draw
the bitmap using a bitmap resource which does not include a colortable
but whose color indices are synchronized to your custom colortable. Now
the problem: if you use pilrc to compile a bitmap with the NOCOLORTABLE
option, then pilrc assumes that you want to use the default system
colortable, and pilrc always changes the color indices of your bitmap
to the closest default system color. Effectively, pilrc has randomized
your colors. The bottom line is, if you use pilrc and want to display a
bitmap using a custom palette, then you can't use the WinPalette function
instead you have to create bitmaps using the COLORTABLE option
and accept the performance penalty.
In order to achieve portability of applications between Palm OS devices
with different color capabilities, Palm OS allows bitmaps to be grouped
together into bitmap families. The bitmap family would contain a copy of
the bitmap at each bit depth. Then the operating system automatically
displays the bitmap possessing the appropriate number of colors for
whichever device the application is running on. Bitmap families are
optional if you try to display a color bitmap on a Palm OS device
which cannot display a color bitmap, the OS will do it's best to reduce
the color depth.
Palm OS supports two types of compression: scan line and RLE.
Use WinDrawBitmap to draw a bitmap. To erase a bitmap use
WinEraseRectangle or redraw the form using FrmDrawForm. To "permanently"
embed a bitmap in a form, define it as a UI object in the form using
the FORMBITMAP identifier in the PilRC file.
MENU ID <MenuResourceId.n>
BEGIN
<PULLDOWNS>
END
<PULLDOWNS>: one or more of:
PULLDOWN <PulldownTitle.s>
BEGIN
<MENUITEMS>
END
<MENUITEMS>: one or more of:
MENUITEM <MenuItem.s> <MenuItemId.n> [AccelChar.c]
When you define a form in PilRC you can associate the form to an MBAR resource.
If you want, you should be able to have multiple menus for the same form.
You can only associate one in the PilRC definition, but supposedly your
app can switch menus using the MenuSetActiveMenu. I've never done it, so
I can't provide sample code.
Palm OS doesn't allow grayed out menu items, and doesn't support
check marks beside menu items, as some other GUIs do. It does allow "keyboard
shortcuts" (Palm Computing's name) which are optional and declared with
the [AccelChar.c] field of the MENUITEM identifier. The keyboard shortcut
allows the user to choose the menu item using the "Command" graffiti stroke
followed by [AccelChar.c].
Don't be confused by guide1.pdf. Guide1.pdf defines two menu UI
resources: menu bar (MBAR) and menu (MENU). Guide1.pdf describes the
pulldowns as separate MENU resources. As far as I can tell, PilRC does
not generate MENU resources. I've inspected PRC files and can't find
any MENU resources. The menus are included within the MBAR resource.
Code is stored as a resource in the database. Simple applications like
those described in this tutorial have two codes resources: code0000 and
code0001. code0000 defines the stack size and memory requirements for
global variables. code0001 is where your application code goes. There are
two limitations on the size of a code resource: the first is the maximum
chunk size, as previously discussed. All Palm OS memory chunks, include
code resources, must be less than 64k bytes. The second limitation is
that the current microprocessor (Motorola 68328) used in the Palm OS
devices has a 16-bit relative jump limitation. That places a limit of
about 32k bytes on the code resource. Because of these limitations,
complicated Palm OS applications must include multiple code resources.
The data0000 resource contains the information used to initialize
the application's global variables.
APPLICATION ID <ApplResourceId.n> <APPL.s>
<APPL.s> must be 4 characters long
The application resource may be used to define the application's
creatorID. I never use this resource I specify the creatorID
when I run build-prc.
ICON "myicon.bmp"
ICONFAMILY "icon1bpp.bmp" "icon2bpp.bmp"
ICONFAMILY "icon1bpp.bmp" "icon2bpp.bmp" "icon4bpp.bmp" "icon8bpp.bmp"
ICONFAMILY "icon1bpp.bmp" "" "" "icon8bpp.bmp" TRANSPARENTINDEX 255
The Palm OS application launcher uses this resource. If an application
has a tAIB resource then the launcher will use it as the application's
icon using standard bitmap commands. Resource ID 1000 is automatically
assigned. ICONFAMILY allows the programmer to create different icons
for Palm OS devices which possess different display capabilities.
APPLICATIONICONNAME ID <AINResourceId.n> <ApplicationName.s>
The Palm OS application launcher uses this resource. If
an application has a tAIN resource, then it will be displayed in the launcher
as the application name. Otherwise, the application launcher uses the name
field from the application's resource database header. I don't use this
resource - the name field in the database works fine.
Now that you know how to create resources and write a PilotMain
procedure, it's time to put these together and build a simple Palm OS
application. A simple application is an application with one form, a
few alert dialogs, one menu, buttons, fields, bitmaps, and labels. We
will build the application in eight steps:
- State the aim of the application
- Design the form
- Design other resources
- Write the header file
- Write the PilotMain function
- Write the form handler function
- Write the makefile
- Testing and debugging
Before starting this chapter, you should have installed prc-tools
and pilrc. The tex2hex source files should be unzipped in a convenient
subdirectory. You should have your editor up and aimed at the tex2hex
source and have a command window open for making the file. You should
also open a paint application and have a look at the two bitmap files,
"text.bmp" and "hex.bmp".
In this chapter code fragments will be interspersed with the
discussion. I've removed the "Figure 6.x.x" labels because they were
annoying. Code is in fixed font, discussion is in proportional font.
It's important to state the aim of the application at
the beginning, especially if you are writing the application for someone
else. If you're just writing for fun, it's still important to know what
you want before you start coding. For this example, we want an application
that converts text strings to their hexadecimal equivalents. I want to
enter a text string, push a button, and see the hexadecimal representation
of the string. In the hexadecimal representation, I want each two character
byte translation separated by hyphens for easy viewing. If someone tries
to convert a null string, an error message should be displayed.
I'm an egotist, so I want an "about" screen which will show my name. To
get to the about screen there must be a menubar with an "about" selection
on it. The menu should also have a "Convert" command, which does the
same thing as the on-screen button.
Just for practice, I want a few bitmaps. At the bottom of the screen
there should be a bitmap of some ASCII characters. Every two seconds it
should change to hexadecimal representation, and vice versa.
I want to be able to copy the text string to the clipboard and paste
text strings from the clipboard to the application. The copy and paste
commands should appear in the menu.
If someone enters something in the field and leaves the application,
it should be there when they relaunch Tex2Hex.
The application needs one form. It doesn't matter whether or not
the form is modal or if it has a frame. I choose a non modal form
with no frame, because I like the look. The form must have a menu bar.
It's non-modal so it can't have a helpstring. It doesn't need a default
button. I'm going to call the form "Tex 2 Hex". I want the form to cover
the whole screen.
The form must have a field for entering the text string. I want the
text field to be centred near the top of the screen, about 100 pixels
wide. This gives it a top left corner around (40 40). I will fine tune
the layout later. The field has to be editable, should be leftaligned
and use the standard font. Underlined is good, so the user knows how much
room is available. Maximum number of characters will be 20 (100 pixels,
average width for standard font is about five pixels). I can change that
later, if I want.
The form must have a field for displaying the hexadecimal
conversion. The hex field should be underneath the text field, centered,
100 pixels wide. Left align the field, use the standard font. Each
text character will be converted to two hex characters plus a space to
separate the hex numbers. So if the text field is twenty characters
wide, the hex field must accomodate sixty characters. At five pixels
per character that won't fit on a single line, so the field must be
multiline. It doesn't have to be underlined. It can't be editable -
the user doesn't change the hex representation.
The form needs a command button to initiate the conversion. The button
should go between the two fields and have the caption "Convert to hex".
The alternating bitmaps will go at the bottom of the screen. Because
they alternate, we won't use FORMBITMAP, but we should make sure that
there's an empty space where we want the bitmaps to be. The bitmaps are
120 pixels wide and 40 pixels high.
The user may want to enter shifted characters, so we should put a
graffitistateindicator in the bottom right corner.
Now we have enough information to write our PilRC code defining
the form:
FORM ID formID_tex2hex AT ( 0 0 160 160 )
NOFRAME
USABLE
MENUID menuID_tex2hex
BEGIN
TITLE "Tex 2 Hex"
FIELD ID fieldID_text AT (30 25 100 AUTO ) USABLE LEFTALIGN FONT 0
EDITABLE UNDERLINED SINGLELINE MAXCHARS 20
BUTTON "Convert to Hex" ID buttonID_convert AT ( 40 48 AUTO AUTO)
USABLE FRAME FONT 0
FIELD ID fieldID_hex AT (30 70 100 50 ) USABLE LEFTALIGN FONT 0
NONEDITABLE MULTIPLELINES MAXCHARS 90
GRAFFITISTATEINDICATOR AT (145 150)
END
This is Tex2Hex version 1.0, so I'll use a tVER resource. I used
Paint to make a customized icon. It's in file "tex2hex.bmp". I used
Paint to make two bitmaps "text.bmp" and "hex.bmp"
that will alternate at the bottom of the screen.
To make the user interface consistent with other applications, I'll
use two pull down menus, "Commands" and "Edit". Commands will include the
Convert and About instructions. Edit will include Copy and Paste. Each
command will have a graffiti shortcut character.
We need two alert resources. We can use an information alert for the
About screen. We can use an error alert if the user tries to convert a
null string.
VERSION "1.0"
ICON "tex2hex.bmp"
BITMAP ID bitmapID_text "text.bmp"
BITMAP ID bitmapID_hex "hex.bmp"
MENU ID menuID_tex2hex
BEGIN
PULLDOWN "Commands"
BEGIN
MENUITEM "Convert" menuitemID_convert
MENUITEM "About" menuitemID_about "A"
END
PULLDOWN "Edit"
BEGIN
MENUITEM "Copy" menuitemID_copy "C"
MENUITEM "Paste" menuitemID_paste "P"
END
END
ALERT ID alertID_about
INFORMATION
BEGIN
TITLE "About Tex2Hex"
MESSAGE "Demo program for the PRC-Tools tutorial"
BUTTONS "Done"
END
ALERT ID alertID_errornullstring
ERROR
BEGIN
TITLE "Error"
MESSAGE "Cannot convert a null string"
BUTTONS "Oops"
END
You have probably noticed that I'm not using literal numbers for
resource IDs. I put all my resource IDs in a header file and include the
header file in both the gcc source file and the PilRC source file. Figure
6.4 shows the header file so far.
#define formID_tex2hex 1000
#define menuID_tex2hex 1000
#define menuitemID_convert 1000
#define menuitemID_about 1001
#define menuitemID_copy 1002
#define menuitemID_paste 1003
#define bitmapID_text 1000
#define bitmapID_hex 1001
#define fieldID_text 1000
#define fieldID_hex 1001
#define alertID_about 1000
#define alertID_errornullstring 1001
#define buttonID_convert 1000
The PilotMain procedure is straight-forward.
UInt32 PilotMain (UInt16 cmd, void *cmdPBP, UInt16 launchFlags)
{
UInt16 error;
if (cmd == sysAppLaunchCmdNormalLaunch)
{
error = StartApplication(); // Application start code
if (error) return error;
EventLoop(); // Event loop
StopApplication (); // Application stop code
}
return 0;
}
StartApplication is unremarkable. OpenDatabase creates the database
if it doesn't exist and creates record zero containing a null string. In
section 3.3 I said that the OpenDatabase procedure usually loads
saved values from record zero, but in this case we won't, because we
will use a special Palm OS memory manager trick which will be described
in section 6.6.
static UInt16 StartApplication(void)
{
UInt16 error;
error = OpenDatabase();
if (error)
return error;
FrmGotoForm(formID_tex2hex);
}
static Boolean OpenDatabase(void)
{
UInt16 index = 0;
MemHandle RecHandle;
MemPtr RecPointer;
char nullstring = 0;
Tex2HexDB = DmOpenDatabaseByTypeCreator(Tex2HexDBType, Tex2HexAppID,
dmModeReadWrite);
if (!Tex2HexDB) {
if (DmCreateDatabase(0, Tex2HexDBName, Tex2HexAppID, Tex2HexDBType,
false)) {
return 1;
}
Tex2HexDB = DmOpenDatabaseByTypeCreator(Tex2HexDBType, Tex2HexAppID,
dmModeReadWrite);
RecHandle = DmNewRecord(Tex2HexDB, &index, 1);
RecPointer = MemHandleLock(RecHandle);
DmWrite(RecPointer, 0, &nullstring, 1);
MemPtrUnlock(RecPointer);
DmReleaseRecord(Tex2HexDB, index, true);
}
return 0;
}
Because there's only one form, the EventLoop doesn't need to
use a switch statement to process the frmLoadEvent. After all,
there's nothing to switch event.data.frmLoad.formID can only
have one value, formID_tex2hex, because that's the only form in the
application. In fact, for single form applications you can move the
FrmInitForm, FrmSetActiveForm, and FrmSetEventHandler form calls into the
StartApplication procedure. Then you don't need to switch on frmLoadEvent
at all and you can delete the FrmGotoForm call from StartApplication. This
optimization will save you some RAM. I've included the switch anyway,
because eventually you will want to write multi-form applications, and
then you'll need it. If you want, after completing this chapter you can
optimize the example by yourself and see how much RAM you can save.
static void EventLoop(void)
{
UInt16 err;
UInt32 formID;
FormPtr form;
EventType event;
do
{
EvtGetEvent(&event, 200);
if (SysHandleEvent(&event)) continue;
if (MenuHandleEvent((void *)0, &event, &err)) continue;
if (event.eType == frmLoadEvent)
{
formID = event.data.frmLoad.formID;
form = FrmInitForm(formID);
FrmSetActiveForm(form);
switch (formID)
{
case formID_tex2hex:
FrmSetEventHandler(form, (FormEventHandlerPtr) tex2hex);
break;
}
}
FrmDispatchEvent(&event);
} while(event.eType != appStopEvent);
}
Note the FldSetTextHandle call in StopApplication. Since we're
using the technique of setting the field handle to record zero's
database handle (which will be explained in section 6.6), we have
to reset the field handle to null before the form is closed. Otherwise
Palm OS would try to deallocate a handle which is still being used
by the database. Strangely, however, in this application you can
leave out the FldSetTextHandle(fieldptr_text, NULL) call and it still
works. Maybe it's because the database closes before the field gets
deallocated. Nevertheless, I recommend leaving the FldSetTextHandle call
in, otherwise you might forget in some other app where it might cause
an error.
static void StopApplication(void)
{
FldSetTextHandle(fieldptr_text, NULL);
DmCloseDatabase(Tex2HexDB);
}
You may have noticed some global variables and a few new definitions
in this code. The global declarations and definitions will go at
the beginning of the tex2hex.c file. The definitions could go in the
tex2hex.h file, if you prefer. One note: if you ever build a large
application which has several header files and many source code files,
you should remember that the extern keyword will cause PilRC to fail,
so external declarations must appear in your C file. Tex2HexAppID and
Tex2HexDBType are used in the DmCreateDatabase call. By specifying
Tex2HexAppID the same as the application's creator ID, Palm OS knows
that this data database belongs to our Text to Hex application.
// FieldPtr and DmOpenRef are defined in the Palm OS header files.
// See if you can find where they are defined.
FieldPtr fieldptr_text;
FieldPtr fieldptr_hex;
DmOpenRef Tex2HexDB;
char Tex2HexDBName[] = "Tex2HexDB";
#define Tex2HexAppID 'TxHx'
#define Tex2HexDBType 'Data'
We're going to use the technique of setting the text field handle
to record zero's database handle. At this point it is important to
understand what a handle is. The Palm OS memory manager divides memory
into "chunks". A chunk is resizable and relocatable. This is important,
because it means that if a chunk is being used for a database record its
size can change if the record gets larger or smaller. And if the chunk
happens to be located between two other chunks, and there isn't enough
room to grow, then the memory manager can relocate the chunk somewhere
else. Every chunk is identified by a unique 32-bit number
this 32-bit number is called the handle. So when we get the handle of
the database record, and then use FldSetTextHandle to the value of the
database record handle, we are telling the Palm OS memory manager that
the field will use the same memory chunk as database record zero. This is
great - when the application closes we don't have to save the contents of
the field because they are already stored in the database record chunk!
But the problem with this technique is that we can't set the field text
handle in the StartApplication procedure because the form has not been
initialized yet. We have to wait until control gets to the form event
handler at that time we know that the form has been initialized
and that space has been allocated for a field UI structure.
Boolean tex2hex(EventPtr event)
{
FormPtr form;
UInt16 handled = 0;
switch (event->eType)
{
case frmOpenEvent:
form = FrmGetActiveForm();
fieldptr_text = FrmGetObjectPtr(form,
FrmGetObjectIndex(form, fieldID_text));
FldSetTextHandle(fieldptr_text,
(MemHandle)DmGetRecord(Tex2HexDB, 0));
fieldptr_hex = FrmGetObjectPtr(form,
FrmGetObjectIndex(form, fieldID_hex));
FrmDrawForm(form);
handled = 1;
break;
case ctlSelectEvent: // A control button was pressed and released.
if (event->data.ctlEnter.controlID== buttonID_convert)
{
Convert();
handled = 1;
}
break;
case menuEvent:
switch (event->data.menu.itemID)
{
case menuitemID_convert:
Convert();
break;
case menuitemID_about:
FrmAlert(alertID_about);
break;
case menuitemID_copy:
FldCopy(fieldptr_text); // copy text user has highlighted
break;
case menuitemID_paste:
FldPaste(fieldptr_text);
break;
}
handled = 1;
break;
case nilEvent:
ChangeBitmap();
handled = 1;
break;
}
return handled;
}
The point of this tutorial is to explain Palm OS programming, not
how to convert ASCII to hex, so I'll just skim over most of the Convert
procedure. The FldGetTextPtr returns a pointer to the character string
that the user input. We assemble the hexadecimal output in the global
string hex[60]. Then use the FldSetTextPtr to point the hex field at
the hex string. We can do this because the hex field is a read only
field. If the user were able to input directly into the hex field, then
we wouldn't be able to use the FldSetTextPtr call. Why? Well, to answer
this, you need to know the difference between handles and pointers.
We already talked about handles the memory manager works
with relocatable, resizable "chunks" of memory and each chunk is
identified by a unique handle. So suppose you want to copy a string
to a database record. A string is a specific section of memory whose
location is given by the string pointer, for instance "hex" in our tex2hex
application. But a handle isn't a specific area of memory chunks
are relocatable. So to copy from a string to a chunk, first you have to
lock the chunk so it doesn't move around, and second you have to find
out exactly where the chunk is. The API function MemHandleLock performs
both these tasks: it locks the chunk and it returns a pointer to where the
chunk is. Go back and have another look at the
OpenDatabase() example. Near the bottom the procedure uses
the MemMove function to copy the game's parameters to record zero of the
database. To do that, the database record has to be fixed in memory:
you can't copy memory to a moving target. So the previous line uses
MemHandleLock to lock the record in place and get a pointer to its
location. After the MemMove, the procedure uses MemPtrUnlock to return
control of the chunk back to the memory manager. If you didn't understand
this, then don't be disappointed, it can take a while to figure this
out. However you must understand the difference between pointers and
handles if you want to program Palm OS effectively.
Another interesting thing to point out is that the hex string must
be global. If it were a local variable, then it would cease to exist
after the function returns. Then the hex field would point at nothing.
void Convert(void)
{
char textchar;
UInt16 textpos;
UInt16 lownybble, hinybble;
char hexchar;
UInt16 textlength;
char* textpointer;
textpointer = FldGetTextPtr(fieldptr_text);
textlength = FldGetTextLength(fieldptr_text);
if (textlength) {
for (textpos = 0; textpos < textlength; textpos ++) {
textchar = textpointer[textpos];
lownybble = textchar % 16;
hinybble = textchar / 16;
if (hinybble < 10) hexchar = 48 + hinybble;
else hexchar = 55 + hinybble;
hex[textpos * 3] = hexchar;
if (lownybble < 10) hexchar = 48 + lownybble;
else hexchar = 55 + lownybble;
hex[textpos * 3 + 1] = hexchar;
hex[textpos * 3 + 2] = 45;
}
hex [textpos * 3 - 1] = 0;
FldSetTextPtr(fieldptr_hex, hex);
FldRecalculateField(fieldptr_hex, true);
FrmDrawForm(FrmGetActiveForm());
}
else {
FrmAlert(alertID_errornullstring);
}
}
void ChangeBitmap(void)
{
MemHandle bitmap_handle;
WhichBitmap = (WhichBitmap + 1) % 2;
if (WhichBitmap == 0) {
bitmap_handle = DmGet1Resource('Tbmp', bitmapID_text);
}
else {
bitmap_handle = DmGet1Resource('Tbmp', bitmapID_hex);
}
WinDrawBitmap (bitmap_handle, 20, 120);
}
The prc-tools tool chain uses the Unix make utility make to make the
application. Make is a complicated utility, I won't try to explain it
here. Try searching google for "make tutorials", and you can read the GNU
make manual here.
Here's a simple Makefile. "all" is a phony target. When a make file
produces more than one output file, then "all" is important. For a single
prc you don't need to use it. The list (ls) command isn't necessary. It's
there because I like to see the filesize of the built prc. In the last
rule, I've used gcc's -c option to separate the compiling and linking.
You don't have to separate the compiling and linking. You could use the
single command m68k-palmos-coff-gcc -O1 tex2hex.c -o tex2hex.
So how does all this work? pilrc compiles the tex2hex.rcp file,
creating a bunch of files all have the suffix "bin". gcc compiles the
code, creating a file named tex2hex. build-prc takes the code and data
resources out of the tex2hex file, and takes all the "bin" files and
creates a prc file.
MAKEFILE WARNING: in a makefile, a tab must appear before every
command. For instance, the first character of line 4 of the makefile is
a tab, the second character is a "b", the third character is a "u", etc.
Some editors automatically replace tabs with spaces. If your editor does
this you will have problems.
I always like to have a "clean" rule. So I can enter "make clean"
and all the temporary files will be removed.
all : tex2hex.prc
tex2hex.prc : tex2hex tfrm03e8.bin
build-prc tex2hex.prc "Text to Hex" TxHx tex2hex *.bin
ls -l *.prc
tfrm03e8.bin: tex2hex.rcp tex2hex.h tex2hex.bmp
pilrc tex2hex.rcp
tex2hex: tex2hex.c tex2hex.h
m68k-palmos-coff-gcc -O1 -c tex2hex.c -o tex2hex.o
m68k-palmos-coff-gcc -O1 tex2hex.o -o tex2hex
clean:
rm -f *.bin *.o tex2hex *.grc *~
Everything's ready to go. Now you should go to your command line and
compile the prc. The next section will be more valuable if you read it
while working on the file.
You read through the entire tutorial, compiled the code in the example,
and it didn't work! Of course not. I'm not a great programmer. I don't
think I've ever had an app work right on the first make. In my opinion
testing and debugging is an essential part of building an application.
I left a couple bugs in the code to illustrate the testing and debugging
function. There were other mistakes on the first compile, but going
through all of them would be too boring. As we identify the bugs and
make corrections, you should correct the source code using your editor.
Before we start, I'll tell you something now that you should remember
whenever the app you're debugging uses a data database. When you debug
you may load the same app into your handheld or emulator again and
again. Reread section 2.2. When an application is hotsync'd to
your Palm or Visor (or loaded into POSE) the previous application is
erased. But it's data database is not erased. If you have a problem with
your database calls, then something like this might happen: load app first
time, find a user interface error, fix the user interface error, load app
second time, app crashes when its launched. The first time you launched
the app it created a bad database. The second time you launched the app,
it tried to load the bad database and crashed. I suggest that if your
application uses a data database, use the memory application to delete
the application before you load in a new copy. The memory application will
delete all data databases having the same Creator ID as your application.
So try make again. GCC barfed lots of stuff. If you're new to C don't
be dismayed in C little mistakes often create tons of error and
warning messages. You've probably just forgotten to terminate a line
with a semicolon. Make it again, this time use the pause key to stop
the display before the first lines scroll off the screen. I see this:
[palm@radagast tex2hex]$ make
m68k-palmos-gcc -O1 -c tex2hex.c -o tex2hex.o
tex2hex.c: In function `ChangeBitmap':
tex2hex.c:34: parameter `Tex2HexDBName' is initialized
tex2hex.c:39: parse error before `{'
tex2hex.c:39: declaration for parameter `PilotMain' but no such parameter
tex2hex.c:36: declaration for parameter `WhichBitmap' but no such parameter
tex2hex.c:35: declaration for parameter `hex' but no such parameter
tex2hex.c:34: declaration for parameter `Tex2HexDBName' but no such parameter
tex2hex.c:33: declaration for parameter `Tex2HexDB' but no such parameter
tex2hex.c:32: declaration for parameter `fieldptr_hex' but no such parameter
tex2hex.c:31: declaration for parameter `fieldptr_text' but no such parameter
tex2hex.c:39: number of arguments doesn't match prototype
cc1: prototype declaration
tex2hex.c:42: `cmd' undeclared (first use in this function)
tex2hex.c:42: (Each undeclared identifier is reported only once
tex2hex.c:42: for each function it appears in.)
tex2hex.c:46: warning: `return' with a value, in function returning void
tex2hex.c:52: warning: `return' with a value, in function returning void
tex2hex.c: In function `OpenDatabase':
tex2hex.c:72: `Tex2HexDB' undeclared (first use in this function)
tex2hex.c:74: `Tex2HexDBName' undeclared (first use in this function)
tex2hex.c: In function `EventLoop':
tex2hex.c:114: parse error before `}'
tex2hex.c: In function `StopApplication':
tex2hex.c:118: `fieldptr_text' undeclared (first use in this function)
tex2hex.c:119: `Tex2HexDB' undeclared (first use in this function)
tex2hex.c: In function `tex2hex':
tex2hex.c:131: `fieldptr_text' undeclared (first use in this function)
tex2hex.c:132: `Tex2HexDB' undeclared (first use in this function)
tex2hex.c:133: `fieldptr_hex' undeclared (first use in this function)
tex2hex.c: In function `Convert':
tex2hex.c:182: `fieldptr_text' undeclared (first use in this function)
tex2hex.c:193: `hex' undeclared (first use in this function)
tex2hex.c:200: `fieldptr_hex' undeclared (first use in this function)
tex2hex.c: At top level:
tex2hex.c:211: redefinition of `ChangeBitmap'
tex2hex.c:31: `ChangeBitmap' previously defined here
tex2hex.c: In function `ChangeBitmap':
tex2hex.c:214: `WhichBitmap' undeclared (first use in this function)
tex2hex.c:217: warning: passing arg 1 of `WinDrawBitmap' from incompatible \
pointer type
make: *** [tex2hex] Error 1
[palm@radagast tex2hex]$
Look for the first line in the file that has an error. Sometimes the
errors aren't reported in order. The first reported error is in line 31,
so look there first. Line 31 looks fine. Work your way back. The previous
line is missing a semicolon. Add the semi-colon and make again.
[palm@radagast tex2hex]$ make
m68k-palmos-gcc -O1 -c tex2hex.c -o tex2hex.o
tex2hex.c: In function `EventLoop':
tex2hex.c:114: parse error before `}'
tex2hex.c: In function `ChangeBitmap':
tex2hex.c:217: warning: passing arg 1 of `WinDrawBitmap' from incompatible \
pointer type
make: *** [tex2hex] Error 1
[palm@radagast tex2hex]$
The first error is "parse error before `}'". Check line
114. It's a "}". There must be something wrong with the {} nesting. This
took a minute or two to figure out. It's the while(event.eType !=
appStopEvent) statement. It's at the end of the "if" statement, it should
be at the end of the "do" statement. The corrected code looks like this:
case formID_tex2hex:
FrmSetEventHandler(form, tex2hexHandler);
break;
}
}
FrmDispatchEvent(&event);
} while(event.eType != appStopEvent);
}
Make it again. Warning from line 219 "tex2hex.c:219: warning:
passing arg 1 of `WinDrawBitmap' from incompatible pointer type", but
the code compiles, links, and builds properly. Lets check the pointer
type. Guide1.pdf says that the argument for WinDrawBitmap should be
of type "BitmapPtr". Guide2.pdf says that DmGet1Resource is of type
"VoidHand". But does DmGet1Resource return a handle or a pointer? Later,
guide2.pdf claims"Result: Returns a pointer to resource data, or nil if
unsuccessful." For now we can ignore the warning and see what happens.
So load, launch, and try converting some numbers. The graphics don't
show up. So the warning was probably important. Use MemHandleLock to
convert the handle to a pointer. The corrected code is below.
static void ChangeBitmap(void)
{
VoidHand bitmaphandle;
BitmapPtr bitmap;
WhichBitmap = (WhichBitmap + 1) % 2;
if (WhichBitmap == 0) bitmaphandle = DmGet1Resource('Tbmp', bitmapID_text);
else bitmaphandle = DmGetResource('Tbmp', bitmapID_hex);
bitmap = MemHandleLock(bitmaphandle);
WinDrawBitmap(bitmap, 20, 120);
MemHandleUnlock(bitmaphandle);
}
Make gives no errors or warnings. Load and launch the application on
POSE. Try to convert a number. The bitmaps work, but they flash too fast
when the field has the focus. The graphics seem to be synchronized with
the cursor. The cursor turning on and off must be accompanied by a nil
event. Cool, I didn't know that. If you don't like the graphic flashing
that fast you can find your own way to deal with it, for instance a
global counter. Next, test all the menu options. Test the grafitti
shortcut commands for the menu options.
Time to test the database. Put something in the text field. Change to
another application. Fine. Change back to Tex to Hex. The text isn't in
the field. (That's the behaviour I got on POSE. I got a "Data manager
error getting record" on my PalmPilot (Palm OS 2.0) unit.) We must
have neglected to do something when we closed the database. Looking up
DmCloseDatabase in Palm_OS_Reference.pdf "This routine doesn't unlock any
records that were left locked. Records and resources should not be left
locked." Is the record locked? Under DmGetRecord Palm_OS_Reference.pdf
says "Return a handle to given record and sets the busy bit for the
record." On page 177, Palm_OS_Companion says "The busy bit is set when
an application currently has a record locked for reading or writing." So
the first time Tex2Hex runs it gets record zero and locks it. The second
time it runs, the record is still locked. The normal way to unlock a
record is to release it before leaving the application. So we add a
DmReleaseRecord call to the StopApplication procedure.
static void StopApplication(void)
{
FldSetTextHandle(fieldptr_text, NULL);
DmReleaseRecord(Tex2HexDB, 0, false);
DmCloseDatabase(Tex2HexDB);
}
Test it again. Everything works. Wow, that was an easy debug. But
next time you'll have to do it by yourself. Don't panic, though. If you
can't debug your code you can post your problem at news.falch.net,
pilot.programmer.gcc. But try to debug the problem yourself first,
because every time you post a stupid question your credibility goes
down. When your credibility hits zero, othersome programmers will put your
name on their kill lists. If you post too many inane questions, you will
be on everybody's kill list. Also, notice that news.falch.net keeps
all the messages since pilot.programmer.gcc was first started. So you
should download all the messages and search them before asking a question.
When everything works, and if you want to
distribute your application, then it's time to connect
to register the CreatorID "TxHx" at Palm Computing's on-line
creatorID registration site. If someone has already claimed your
creatorID, then you have to choose an unused creator ID and make the
appropriate changes to tex2hex.h and Makefile. Then you can distribute
your application. If you want, as an exercise you could go to the
CreatorID
registration site and find out if the creator ID TxHx is registered.
Maybe later I'll write some instructions about how to use gdb with
prc-tools. But for now, I'll just offer my old techniques for in-line
debugging code. I usually keep these four lines commented out at the
end of every application, then I can copy them wherever I need them.
// char debugstring1[20], debugstring2[20];
// StrIToA(debugstring1, **111**);
// StrIToA(debugstring2, **222**);
// ErrDisplayFileLineMsg (debugstring1, **333**, debugstring2);
They can be used to enter breakpoints in your code. The first line
has to be copied to the top of your source code where you declare global
variables. The next three lines go where you want the breakpoint. You
can display three integers. Replace **nnn** with the variable names of the
integers you want displayed. Another useful call is ErrFatalDisplayIf. The
prototype is:
void ErrFatalDisplayIf (Boolean condition, char* message)
The boolean test is useful for entering conditional
breakpoints. Suppose you don't know exactly why an application is
misbehaving, but you do know that bad things will happen if the integer
"MyNumber" exceeds 250. Then you can use this code snippet to display
the value of "MyNumber" if it exceeds 250:
StrIToA(debugstring1, MyNumber);
ErrFatalDisplayIf( MyNumber > 250, debugstring1);
That's the end of this tutorial. If you find out something new,
remember to post it at news.massena.com.
Andrew Howlett
Owen Sound, Canada
March 2001
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
-
This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
-
You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
-
You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
-
You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
-
You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
-
If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
-
You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
-
Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange;
or,
-
Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
-
Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
-
You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
-
You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
-
Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
-
If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
-
If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
-
The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
-
If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
|