The "Tables" application

The "Tables" application

Controls represent the primary method of interaction between the user and a PalmOS program. Tables are a particular kind of highly customizable, highly flexible control that deserve their own sample program. Again, we begin with resources.

Resources and Constants:

Constants:


class TABLE_CONSTANTS

feature {ANY} -- constants

   Table_main_menu_bar : INTEGER_16 is 1000
   Table_menu_about : INTEGER_16 is 1001
   Table_main_form : INTEGER_16 is 1200
   Main_form_table : INTEGER_16 is 1201
   Main_form_field : INTEGER_16 is 1202
   Main_form_list : INTEGER_16 is 1203
   Table_about_alert : INTEGER_16 is 1300

end


Resources:


#include "table_resource.h"

MENU ID Table_main_menu_bar
BEGIN
   PULLDOWN "Help"
   BEGIN
      MENUITEM "About..." ID Table_menu_about "A"
   end
end

form id Table_main_form AT (0 0 160 160)
menuid Table_main_menu_bar
begin
   title "ePalm table demo"
   label "Top center text:" autoid at (0 141)
   field ID Main_form_field at (PREVRIGHT+10 PREVTOP 30 AUTO) noneditable singleline underlined maxchars 10
   table id Main_form_table at (0 21 160 120) ROWS 8 COLUMNS 9 COLUMNWIDTHS 25 6 12 6 25 25 19 25 15 
   list "1" "2" "3" "4" "5" id Main_form_list at (108 21 25 120) visibleitems 5 nonusable
end

alert id Table_about_alert
information
begin
   title "About TableDemo"
   message "Created using ePalm, a port of SmallEiffel for the PalmOS"
   buttons "OK"
end


Note that not a great deal of information was given in the resource description for the table resource; that's because while the skeleton of the table is described in the resource, the population and appearance of the table are done by code.

The main application

Again, the root object/application is not much different; it overrides the same methods and provides the same structure as earlier, so will not be displayed here.

The event handler

The event handler for the table sample application does have a few minor new twists, the only one of significance being this:


handle_frm_open_event( event :EVENT ) is
   do
      form_manager.active_form.draw     
      create demo_table.from_shared_memory( form_manager.active_form.table_from_id( Main_form_table ).opaque_pointer )
      demo_table.initialize( form_manager.active_form )
      form_manager.active_form.draw
      last_event_was_handled := True
   end


The table object is created and manually initialized before the form is drawn, which is an important wrinkle that's easy to miss. Finally, the real meat of this application comes in the form of the DEMO_TABLE class.

The table itself

The DEMO_TABLE class encapsulates the table itself:


class DEMO_TABLE

inherit
   TABLE_CONSTANTS
   TABLE_STYLES
   TABLE_DISPATCHER_CLIENT
   WINDOW_MANAGER_CLIENT
   FORM_MANAGER_CLIENT
   FORM_TABLE
      redefine
         draw_item, load_data, save_data
      end

creation   
   from_shared_memory

feature {ANY}

   draw_item( row, column : INTEGER_16; drawn_item_bounds: PALM_RECTANGLE ) is 
      local
         x1, y1, x2, y2 : INTEGER_16
      do 
         if column = 8 then
            inspect item_integer( row, column )
            when 0 then
               x1 := drawn_item_bounds.left
               y1 := drawn_item_bounds.top
               x2 := drawn_item_bounds.right
               y2 := drawn_item_bounds.bottom
            when 1 then
               x1 := drawn_item_bounds.left + ( drawn_item_bounds.width // 2 )
               y1 := drawn_item_bounds.top
               x2 := x1
               y2 := drawn_item_bounds.bottom
            when 2 then
               x1 := drawn_item_bounds.right
               y1 := drawn_item_bounds.top
               x2 := drawn_item_bounds.left
               y2 := drawn_item_bounds.bottom
            when 3 then
               x1 := drawn_item_bounds.left
               y1 := drawn_item_bounds.top + ( drawn_item_bounds.height // 2 )
               x2 := drawn_item_bounds.right
               y2 := y1
            else
               x1 := drawn_item_bounds.left
               y1 := drawn_item_bounds.top
               x2 := x1
               y2 := y1
            end
            window_manager.draw_line( x1, y1, x2, y2 )
         end       
      end

   handle_column_from_table_column( table_column : INTEGER_16) : INTEGER_16 is
      --return the handle column (1-3; eiffel arrays start at 1)
      do
         inspect 
            table_column
         when 0 then
            Result := 1
         when 5 then
            Result := 2
         when 7 then
            Result := 3
         end
      end

   handle : MEM_HANDLE

   load_data( row, column : INTEGER_16; is_editable : BOOLEAN; field : FORM_FIELD ) is 
      do 
         if handle = Void then 
            create handle.allocate( 5, True )
            handle.set_to_string("bob")
         end
         last_load_data_was_successful := True
         last_load_data_handle := handles.item( handle_column_from_table_column( column )).item(row + 1)
         --add 1 to row because eiffel arrays start at 1
         last_load_data_size := last_load_data_handle.size.to_integer_16
         last_load_data_offset := 0
      end

   save_data( row, column : INTEGER_16 ) is 
      local
         handle_column : INTEGER_16
         field : FORM_FIELD
         s : STRING
      do 
         table_should_be_redrawn_after_save := False
         handle_column := handle_column_from_table_column( column )
         if ( row = 0 and handle_column = 2 ) then
            field := current_field
            if field.opaque_pointer.is_not_null and then field.is_dirty then
               s := handles.item( handle_column ).item( row+1 ).to_string
               s.to_lower
               handles.item(handle_column).item(row+1).set_to_string( s )
               mark_row_invalid( row )
               table_should_be_redrawn_after_save := True
               form_manager.active_form.field_from_id( Main_form_field ).set_text_to_string( s )
               form_manager.active_form.field_from_id( Main_form_field ).draw
            end
         end
      end

feature {ANY}   -- initialization

   handles : ARRAY[ ARRAY[ MEM_HANDLE ] ]
         --holds text handles used in the table

   allocate_handles is
      local
         i, j : INTEGER_16
         h : MEM_HANDLE
         new_array : ARRAY[ MEM_HANDLE ]
      do
         create handles.make( 1, 3 )
         from i := 1 until i = 4 loop
            create new_array.make( 1, 12 )
            handles.put( new_array, i )
            from j := 1 until j = 12 loop
               create h.allocate( 2, True )
               handles.item( i ).put( h, j )
               handles.item(i).item(j).set_to_string( "" )
               j := j + 1
            end
            i := i + 1
         end
      end

   initialize( form : FORM ) is
      local
         row : INTEGER_16
         column : INTEGER_16
         num_rows : INTEGER_16
         labels : ARRAY[STRING]
         list : FORM_LIST
      do
         if handles = Void then allocate_handles end
         labels := << "0", "1", "2", "3", "4", "5", "6", "7" >>
         --set column widths.  For no apparent reason, this is required.
         --it should not be
         set_column_width( 0, 25 )
         set_column_width( 1, 6 )
         set_column_width( 2, 12 )
         set_column_width( 3, 6 )
         set_column_width( 4, 25 )
         set_column_width( 5, 25 )
         set_column_width( 6, 19 )
         set_column_width( 7, 25 )
         set_column_width( 8, 15 )

         list := form.list_from_id( Main_form_list )

         num_rows := number_of_rows

         from
            row := 0
         until
            row = num_rows
         loop
            set_item_style( row, 0, Text_with_note_table_item )

            set_item_style( row, 1, Numeric_table_item )
            set_item_integer( row, 1, row )

            set_item_style( row, 2, Checkbox_table_item )
            set_item_integer( row, 2, row \\ 2 )

            set_item_style( row, 3, Label_table_item )
            set_item_pointer( row, 3, labels.item( row+1 ).to_external )
            --use "row+1" above because eiffel arrays start with item 1!

            set_item_style( row, 4, Date_table_item )
            set_item_integer( row, 4, 0 )

            set_item_style( row, 5, Text_table_item )

            set_item_style( row, 6, Popup_trigger_table_item )
            set_item_integer( row, 6, row \\ 5 )
            set_item_pointer( row, 6, list.opaque_pointer )

            set_item_style( row, 7, Narrow_text_table_item )
            set_item_integer( row, 7, row*2 )

            set_item_style( row, 8, Custom_table_item )
            set_item_integer( row, 8, row \\ 4 )

            set_row_is_usable( row, True )
            set_row_is_selectable( row, True )
            set_row_height( row, 11 )
            mark_row_invalid( row )

            row := row + 1
         end

         set_row_is_usable( 1, False )

         from
            column := 0
         until
            column = 9
         loop
            set_column_is_usable( column, True )
            column := column + 1
         end

         table_dispatcher.register_table_with_custom_load_data_procedure( Current, 0 )
         table_dispatcher.register_table_with_custom_load_data_procedure( Current, 5 )     
         table_dispatcher.register_table_with_custom_load_data_procedure( Current, 7 )     
         table_dispatcher.register_table_with_custom_draw_event( Current, 7 )
         table_dispatcher.register_table_with_custom_draw_event( Current, 8 )
         table_dispatcher.register_table_with_custom_save_data_procedure( Current, 5 )
      end   
end


As you can see, there's a lot going on here.

draw_item

When PalmOS draws a table to the screen, each cell has a format which describes the data contained therein. For the most part, the supplied formats (numbers, strings, etc) can take care of most user requests.

The draw_item feature shows how user programs can customize how certain cells of a table are drawn. The overridden draw_item feature is called via callback from PalmOS when it is drawing the table; any cells marked as being custom will be forwarded to draw_item instead. By looking at the row and column that will be drawn, and a rectangle representing the screen coordinates to be drawn on, this feature can do pretty much whatever it wants (draw lines, bitmaps, whatever). Here, it simply draws a line in the supplied rectangle.

handle_column_from_table_column

The handle_column_from_table_column deserves a bit of explanation. The developer must provide a handle for every string represented in the table; this function simply maps columns in the table to columns in an array of handles (called, predictably, handles).

Loading and Saving Data

load_data is another redefined feature. Since data for strings in the table is held outside of the table itself, callbacks must be invoked to load the data from the handles into the table. This is done with the load_data feature, which stores the value of the handle in last_load_data_handle and the size of the data in last_load_data_size. It's important and confusing to note that Eiffel arrays typically begin with index 1, and C arrays (which is what the table is expecting) begin with 0, so don't forget to translate from one to the other when setting things!

save_data is the converse; when data has been changed, it must be manually saved and the table marked for redraw afterwards.

Initialization

The initialize feature is simple, but does a lot of work, manually setting column widths, initializing lists, setting item styles and values, registering the table with custom load, save, and draw procedures, etc. It's important to spend a lot of time here and make sure your table is set up correctly; failure to do so can result in difficult-to-diagnose errors!

New features for cecil.se

The cecil.se file takes on a few more features in the table application:


--the name of the include C file
epalm_cecil.h
--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
TABLE_DISPATCHER_CLIENT_table_dispatcher TABLE_DISPATCHER_CLIENT table_dispatcher
TABLE_DISPATCHER_dispatch_custom_save_data_event TABLE_DISPATCHER dispatch_custom_save_data_event
TABLE_DISPATCHER_dispatch_custom_load_data_event TABLE_DISPATCHER dispatch_custom_load_data_event
TABLE_DISPATCHER_dispatch_custom_draw_event TABLE_DISPATCHER dispatch_custom_draw_event


The FORM_DISPATCHER and FORM_DISPATCHER_CLIENT entries are common to almost all ePalm programs, but only include the TABLE_DISPATCHER and TABLE_DISPATCHER_CLIENT features in programs which need them.

Modification to the .ace file

Finally, the .ace file has to include the following in order for the table support code to be included in the executable:


external -- section for elements written in another language

   cecil ("cecil.se")
   external_header_path: "${EPALM}/smalleiffel_sys/runtime/c ${EPALM}/src/c_code ."
   external_c_files: "${EPALM}/src/c_code/table_support.c"


The table_support.c file contains code implementing table callbacks and must be included in your project or the compilation will fail.

Powered by Zope