Hello, World!

Hello, World!

The first sample program will be fairly simple: the program fills the screen with a form entitled "Hello World!" and a button; when pressed, the button brings up an alert box with the words "Goodnight moon!". A simple menu that displays a few choices which the user can select.

This is the sample application from Palm Programming: The Developer's Guide beginning on page 67 of the first edition, and we will walk through it in a similar fashion.

Forms and Menus: hello_world.rcp and hello_world_resource.h

The first task is to describe the forms and menus to the PalmOS development environment. We use the pilrc program from Ardiri software for this, available at www.ardiri.com. First, we define the constants we'll be using to describe all the resources, and list them in the C-style header file 'hello_world_resource.h':


#define HelloWorldForm                            1000    
#define HelloWorldButtonButton                    1003    

#define GoodnightMoonAlert                        1101
#define GoodnightMoonOK                           0
#define DebugAlert                                1102
#define DebugOK                                   0

#define HelloWorldMenuBar                         1000

#define FirstMenu                                 1010
#define FirstBeep                                 1010

#define SecondMenu                                1000
#define SecondBeepmore                            1000


Good so far. These numbers represent internal integer codes for options the user can choose. Note that there are duplicates--OK is generally represented by the value 0, and there are no real problems with objects of different types holding similar IDs in most cases.

Now we must represent the forms and menus themselves, in the 'pilrc'-compatible file 'hello_world.rcp':


#include "hello_world_resource.h"

MENU ID HelloWorldMenuBar
BEGIN
    PULLDOWN "First"
    BEGIN
        MENUITEM "Beep" ID FirstBeep
    END

    PULLDOWN "Second"
    BEGIN
        MENUITEM "Beep more" ID SecondBeepmore
    END
END

FORM ID HelloWorldForm AT (0 0 160 160)
MENUID 1000
BEGIN
    TITLE "Hello World"
    BUTTON "Button" ID HelloWorldButtonButton AT (59 91 36 12) LEFTANCHOR FRAME FONT 0
END

ALERT ID GoodnightMoonAlert
INFORMATION
BEGIN
    TITLE "An alert"
    MESSAGE "Goodnight moon!"
    BUTTONS "OK"
END

ALERT ID DebugAlert
INFORMATION
BEGIN
    TITLE "Debug info"
    MESSAGE "bk file: ^1, line: ^2"
    BUTTONS "OK"
END

VERSION ID 1000 "1.0"


Note that the textual descriptions of resource values are used here; pilrc will take the numerical values from hello_world_resource.h and use them in the generated binary resource. We are defining a number of resources here: a menu bar, a form, and an alert.

We're not quite done, though--we must still generate an Eiffel class representing the resource constants, or we can't use them in our programs. The easiest way to do this is establish a Makefile target using the make_constants.rb ruby script provided in the tools subdirectory of the distribution:


$(PROGNAME)_constants.e :
        $(RUBY) $(EPALM_TOOLS)/make_constants.rb $(PROGNAME)_constants $(PROGNAME)_resource.h


...where $(RUBY) is set to point to the ruby executable, $(EPALM_TOOLS) is the path to the ePalm tools directory, and $(PROGNAME) is the name of the program--in this case, hello_world.

The application class

Every ePalm application inherits from the EPALM_APPLICATION class, and then customizes a few of its deferred features. Here's the MAIN class from 'hello_world':


class MAIN

inherit   
   FORM_DISPATCHER_CLIENT
   HELLO_WORLD_CONSTANTS
   PALM_APPLICATION
      redefine
         handle_event, start_application
      end

creation
   pilot_main

feature {ANY} --redefined application feature   

   start_application is
         --opens the "hello world" form
      do
         form_manager.go_to_form( Hello_world_form )
      end

   handle_event( event : EVENT ) is
         --handles events
      local
         form : FORM
      do
         inspect event.type
         when Frm_load_event then
            create form.from_form_id( event.data_frmload_formid )
            form.set_as_active_form
            create form_handler
            form.set_event_handler( form_handler )
            last_event_was_handled := True
         else
            last_event_was_handled := False
         end
      end

   form_handler : MAIN_FORM_EVENT_HANDLER

end


Two features are overridden: start_application invokes the form_manager singleton object of FORM_MANAGER_CLIENT to open our starting form, Hello_world_form. It does nothing else.

The handle_event feature takes a bit more explanation; it handles high-level events such as those requesting forms be opened, etc. In this case, it looks form a Frm_load_event event type, which indicates that another form should be opened. When the OS requests that a form be opened, we have a pretty good idea what form it is (since we've only defined one). We create a form object from the form id noted in the event (event.data_frmload_formid), and set it as the active form. We then create form_handler (our application's form event handler, of type MAIN_FORM_EVENT_HANDLER and set the form's event handler to be that object, and exit.

The OS does almost everything else. The only thing we haven't covered is how our program responds to button presses.

'MAIN_FORM_EVENT_HANDLER': The form event handler

The answer is in our final Eiffel class, MAIN_FORM_EVENT_HANDLER. When PalmOS sees an event, it tries to handle it at the lowest possible level; if it's a form-type event, it passes it off to the current active form handler for the form in question. MAIN_FORM_EVENT_HANDLER inherits from STANDARD_EVENT_HANDLER and overrides a number of its event handlers:


class MAIN_FORM_EVENT_HANDLER

inherit
   FORM_MANAGER_CLIENT
   HELLO_WORLD_CONSTANTS
   SOUND_MANAGER_CLIENT
   SOUND_MANAGER_CONSTANTS
   STANDARD_EVENT_HANDLER   
      redefine
         handle_frm_open_event,
         handle_ctl_select_event,
         handle_menu_event
      end

feature {ANY}

   handle_frm_open_event( event : EVENT ) is
      do
         form_manager.active_form.draw
         last_event_was_handled := True
      end

   handle_ctl_select_event( event : EVENT ) is
      do
         form_manager.alert( Goodnight_moon_alert )
         last_event_was_handled := True
      end

   handle_menu_event( event : EVENT ) is
      do
         if event.data_menu_item_id = First_beep then
            sound_manager.play_system_sound( Snd_info );
         else
            sound_manager.play_system_sound( Snd_start_up );
         end
         last_event_was_handled := True
      end

end


Looking at each of these in turn is at least somewhat illustrative.

The handle_frm_open_event feature handles the low-level event of a form actually being opened (this is different from Frm_load_event, which is handled at the application level and requests that a form be loaded from memory; the Frm_open_event event type represents a form about to be opened to the screen). Its action is simple; draw the active form and record that the event was handled properly. Setting last_event_was_handled to True ensures that no further handling of this event will occur; if it is left as False, then the OS will try and handle the event further and further up the chain of command.

The handle_ctl_select_event feature handles any events in which the user selects a control on the page; in this case, we have only one control on the page, the button; when pressed, this feature brings up an alert dialog (our 'Goodnight_moon_alert), which is handled by the OS, and then records that the event was handled properly.

Finally, handle_menu_event handles any events created by the menu selections. The event.data_menu_item_id feature holds the ID of the menu item selected; this feature simply branches based on that value and plays a sound.

Callbacks and such: cecil.se

You will have to write a custom cecil.se file to alert SmartEiffel that certain Eiffel functions will be called from C code. The cecil.se file here is quite simple and only uses some FORM_DISPATCHER and FORM_DISPATCHER_CLIENT features:


--the feature you want to call from C
FORM_DISPATCHER_dispatch_event_from_pointer FORM_DISPATCHER dispatch_event_from_pointer
FORM_DISPATCHER_CLIENT_form_dispatcher FORM_DISPATCHER_CLIENT form_dispatcher


The .ace file

All of this can now be bound into an .ace file, so:


system
   hello_world

root

   MAIN : "pilot_main"

default -- section of the system

   assertion (boost);
   debug (no);
   trace (no);
   collect (yes);
   case_insensitive (no);
   no_style_warning (no);
   no_warning (no);
   verbose (no);
   manifest_string_trace (yes);
   high_memory_compiler (no);     

cluster -- section

   hello_world : "."
   --these directories will point to your ePalm distribution
   epalm_application : "../../src/application"
   epalm_constants : "../../src/constants"
   epalm_structs : "../../src/structs"
   --these directories point to your SmartEiffel distribution
   "/home/vputz/bin/SmartEiffel/lib/kernel";
   "/home/vputz/bin/SmartEiffel/lib/base";
   "/home/vputz/bin/SmartEiffel/lib/io"; 

external -- section for elements written in another language

   cecil ("cecil.se")

generate -- section

   no_strip(no);
   no_split(no);
   clean(no);
   c_mode: palmos
end


As you can see, the basics of a simple ePalm application are just that: simple. Define the forms and resources your application will use, define an application which will request your title-page form and also handle high-level "load form" events, and then for each form define an event handler which will handle the low-level events produced by the user.

It should be noted that Makefiles can automate a great deal for the user, particularly generation of source code. also, some environment variables should be set; for example, SmartEiffel should point to the smalleiffel_sys/system.se file in the ePalm distribution, and for proper operation of many Makefile targets and tools, the EPALM variable should point to the root directory of your ePalm distribution.

Powered by Zope