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.
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
#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.
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 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 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
).
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.
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!
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.
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.