PALM TABLES IN QUARTUS FORTH

Copyright 2000 T. Steele ts@mechengineer.freeserve.co.uk
Pilot, PalmPilot, PalmOS and Palm Computing are trademarks of Palm Computing and 3Com Corporation
This information may not be sold for profit.

1. Administration

1.1 DISCLAIMER

This tutorial will explain how I have used tables in Quartus Forth. It will present a very simple application that uses a table. The method presented in this memo is not the only way of implementing tables, however it does work.

I have only scratched the surface with tables, so this document will not explain everything about a table. However it does give the recreational programmer a starting point. It is expected that after mastering the techniques described here, the reader will find better and more sophisticated ways to manipulate tables.

1.2 PLATFORM

This document assumes that the user has Quartus Forth version 1.2.6R and is using Palm OS up to and including 3.5.. It should be noted that tables using callbacks will work well in version of the Palm OS up to and including OS3.0. For later versions, up to and including OS3.5, a fix for callbacks kindly coded by Steve Bohrer will solve any problems. However this may not work for later versions of Quartus Forth. It can be found in the accompaning Forth library files.

1.3 PREREQUISITES

Readers should be familiar with Quartus Forth and be able to code simple applications including the generation of Palm resources. Help in learning Quartus Forth can be had by visiting the Quartus home page at http://www.Quartus.net and following some of the links there, as well as checking out the excellent discussion forum. For producing resources I have used OnBoard RsrcEdit by Roger Lawrence which can be found at http://www.individeo.net and this excellent piece of software runs on the Palm device itself.

1.4 THANK YOU

Thanks is due in no small part to Neal Bridges for all his help with both learning Forth and for the libraries which I have used which are copyright Neal Bridges 1998 1999. Also to Steve Bohrer for his Callback library which provides the necessary fix for using callbacks with the Palm operating systems.

2. Organize your data

2.1 WHAT IS A TABLE?

A table is a way of organizing data on the screen. For instance the Address List (press the Address hardware button on your Pilot) presents your address data using a table of three columns and eleven rows. An intersection of a column and a row is called a cell. Cells may also be called "table items". According to the PalmOS manual, the cells may contain other UI objects such as fields and buttons. This explanation is limited to cells containing text.
The PalmOS manual states that the table may be larger than the LCD panel. To save resources however it is normal practice to make the table resource the size which the developer wishes to display on the screen. It is then a matter of storing the appropriate data in the table at any particular instant for display. The table resource in fact acts as a window to display the table data, with data being taken out and added as the table is scrolled. This method has the added benefit of not having to redefine the table resource if the number of rows is increased.

When you tap in one of the table’s cells PalmOS enters a tblSelectEvent on the event queue. The tblSelectEvent data structure includes the row and column of the cell that was tapped. This allows you to execute different actions for each column, row, and/or cell. In this way the table can be thought of as a grid of buttons.

2.2 ORGANIZING THE DATA

Before writing any code, you must plan the presentation of your data. The table below illustrates a planning tool which can be used for the planning of Palm tables.
 

Data Source Character Width

Pixel width

Comments
Required Assigned
Field One about 9   45  
Field Two        
Field Three        
       
Total: about 27   135  

In most tables, each row is associated with a record of information. The record has several fields. The problem is to figure out which fields will be displayed, how many characters of each field will be displayed, the number of pixels required to display the entire field and the pixel width assigned to the field.
Some things to remember when planning your column widths:

Next is to figure out the height of the table. The PalmPilot has 160 pixel-rows. If you want the form to have a title bar, subtract 15 pixel-rows. Many people want on-screen buttons or a message area at the bottom of the LCD panel – subtract another 15 pixel-rows. Now you’re down to 130. Standard and bold font are both 11 pixels high, so you can fit 11 rows in the table. Some applications may require column headings. If so, subtract a row for that.

2.3 DEFINE THE RESOURCE

If you have organized your data properly, then you will be able to define the table UI object. To do this you can use PilRC on your PC, or my preferred method is to use OnBoard RsrcEdit.

2.4 EXAMPLE

For our fairly trivial example, we will produce a table which will display the cell row and column in each cell and will be capable of being scrolled up and down. The 160 pixel-width will be divided as follows:
 

Data Character Width

Pixel width

Comments
Required Assigned
Cell row and column 9 45 45  
Cell row and column 9 45 45  
Cell row and column 9 45 45  
Total 27 135 135  

Our form will need a title bar, and we want some room at the bottom of the form to display other information and maybe controls. So we have 130 pixel-height to work with, which gives us eleven rows in the table (using standard and bold fonts).

We now have all the information we need to define the table resource and it's form. I have given the details below for a table resource and the associated scrollbar produced with OnBoard RsrcEdit. This resource is also included in the zip file as the file TableRsrc.prc along with this document, in case of difficulty (or lazyness).

Database
Title: TableRsrc
Creator: p4ap
Type: ABCD (or any other suitable type but don't forget to alter the table memo accordingly)
ResourceDB: checked

The database contains tFRM 1100
ID: 1100
Type: tFRM
top: 0
width: 160
left: 0
height: 160
usable: checked
All other properties are set to 0

The form contains

Title
Table Example

Table2000
top: 26
width: 141
rows: 17
left: 10
height: 108
ID: 2000
Editable: checked
Add 3 columns each 45 wide

ScrollBar2001
top: 26
width: 7 (all scrollbars should be set to 7)
ID: 2001
left: 152
height: 108
Usable: checked
Set Value Min Value etc to 0

3. Drawing the Table

3.1 GENERAL

If your form contains a table, then the drawing requirements are more complicated than might normally be expected. If the style is set to "customTableItem" we must create a "DrawTable" word for the form containing the table. I have found this to be the most useful type of style. The DrawTable word must do at least six things:

  1. Set rows usable/non-usable
  2. Mark rows invalid
  3. Set columns usable/non-usable
  4. Set the style for each cell
  5. Set the draw procedure for each cell
  6. Call TblDrawTable

3.2 SET ROW USABLE

PalmOS displays only rows that are usable. The number of usable rows may change while your application is running. For instance, if you have twenty items to list and only eleven row in your table, then your application will initially display the first eleven items. So initially all eleven rows are usable. Then the user presses the page down key and your application displays the final nine items. So the top nine rows are usable (rows zero to eight) and the bottom two rows are non-usable (rows nine and ten). For the example, as the table is always full of data all the rows are set usable.

You set rows usable/non-usable with the TblSetRowUsable API call. This is used as follows:

TblSetRowUsable ( usable? row &table. -- )

Row is zero based (first row is row zero) and usable is 1 for usable, 0 for non-usable.

3.3 MARK ROW INVALID

If you set the row usable, then PalmOS will draw that row. But if you need to redraw the table later on, Palm OS will only redraw rows that are marked "invalid". "Invalid" means that the data source has been changed implying that the data displayed by the table in that row is no longer the same as the data source. You mark a row as "invalid" using the TblMarkRowInvalid API call. Its stack diagram is:

TblMarkRowInvalid ( row &table. -- )

3.4 SET COLUMN USABLE

In the same way that PalmOS only displays "usable" rows, PalmOS only displays "usable" columns. So you can turn entire columns off if you want to. Consider a sophisticated table application where the user gets to choose which data will be represented in each column. The user may choose to turn a column off. Often all the columns are set usable. The call is TblSetColumnUsable and its stack diagram is:

TblSetColumnUsable ( usable? row &table. -- )

3.5 SET ITEM STYLE

The DrawTable word must specify the style of each usable cell. Styles are defined in "table.h". If the item style is set to "customTableItem", a value of 1, this forces PalmOS to use your custom cell draw procedure. TblSetItemStyle must be called for every usable cell. Its stack diagram is:

TblSetItemStyle ( type[>byte] column row &table. -- )

In Quartus Forth type[>byte] becomes 1 >byte for customTableItem.

3.6 SET CUSTOM DRAW PROCEDURE

If the item style is "customTableItem" then PalmOS must use the application defined custom cell drawing procedure. I have found "customTableItem" to be the most useful style and my custom cell drawing procedures will be explained later. You have to define the custom cell drawing procedure for each column. The stack diagram is:

TblSetCustomDrawProcedure ( &drawCallback. column &table. -- )

3.7 DRAW THE TABLE

After all the table parameters have been set, our DrawForm procedure uses the TblDrawTable API call to draw the table. The stack diagram is:

TblDrawTable ( &table. -- )

3.8 EXAMPLE

We shall examine the table.txt file in detail. The point of entry is the word called main:

: main
  allocCbStack tableform showform 
  initialise-table initialise-scrollbar
  DrawTable
  begin 
    ekey do-event
  again ;

Going through main word by word, the first word is allocCbStack which is from the library callbacks and is necessary since the custom draw procedure uses callbacks and these will only work in OS versions greater than OS 3.0 if this library is used. Next the table form is displayed with tableform showform and then the table and scrollbars are initialised and finally the table is drawn.

: initialise-table ( -- )
  tableID GetObjectPtr _tableptr 2!
  initialise-rows initialise-cols ;

Here we initialise the rows and then the columns.

: initialise-rows ( -- )
  -1 tableheight ! visiblerows 0 do
      true i tableptr TblSetRowUsable
      i tableptr TblMarkRowInvalid
      true i tableptr
      TblSetRowSelectable 
\ set item style (CustomTableItem)
     numcols 0 do
      1 >byte i j tableptr 
      TblSetItemStyle
    loop
\ set row height & calculate table ht
      rowheight dup i tableptr 
      TblSetRowHeight tableheight +!
  loop ;

Here we begin by storing -1 in the variable tableheight to correct the final calculated height value which is 1 pixel too large. If we are not drawing a grid around the cells then this calculation can be omitted. Next we loop through all the rows and set them usable/non-usable, and if usable set them invalid and set the item style. The physical number of rows in the table resource is the constant visiblerows.

:  initialise-cols ( -- )
  0 tablewidth ! numcols 0 do
\ set columns usable 
     true i tableptr
     TblSetColumnUsable
     1 i tableptr TblSetColumnSpacing
\ set custom draw procedure
     ['] drawcell xt>abs i tableptr 
     TblSetCustomDrawProcedure
\ calculate width for TableFrame
    i tableptr tblGetColumnWidth 1+
    tablewidth +!
  loop -1 tablewidth +!  ;

Here we begin by storing 0 in the variable tablewidth to initialise it for the calculation of the table width. If we are not drawing a grid around the cells then this calculation can be omitted. Next we set the columns usable and set the column spacing. We then have to set the custom cell draw procedure for each column. The custom cell draw word is drawcell and we place the execution token for the word on the stack and then convert it to an absolute 32 bit address to pass to the API call TblSetCustomDrawProcedure.

variable sclpage
variable sclmax
variable sclmin
variable sclval
0 sclmin !
0 sclval !

: initialise-scrollbar ( -- )
\ Set scrollbar variables based on
\  the  total number of rows and 
\ visible rows
  scrollbar GetObjectPtr _scrollptr 2!
  numrows visiblerows - dup
  sclmax ! visiblerows min sclpage !
  sclpage @ sclmax @ sclmin @ sclval
  @ scrollptr SclSetScrollbar scrollptr
  SclDrawScrollBar ;

To avoid having to reset the scrollbar variables every time we make an alteration to the number of rows in our table, we can calculate these values when we initialise the scrollbar. From the Palm SDK reference the max parameter is computed as:
number of lines of text - page size + overlap
where number of lines of text is the total number of lines or rows
needed to display the entire object, page size is the number of lines
or rows that can be displayed on the screen at one time, and overlap
is the number of lines or rows from the bottom of one page to be
visible at the top of the next page.
Note that in our example we have no overlap.

: DrawTable ( -- )
  tableptr 2dup TblEraseTable
  TblDrawTable  DrawTableFrame
  HighlightTblItem ;

To draw the table we must first erase the table so that we are not writing on our existing text. A call to TblDrawTable draws the table and DrawTableFrame draws the frame around the table. The reason for the DrawTableFrame is that the Palm OS refuses to draw bits of the outside of the table grid and this is my workaround the problem. Finally we must highlight the selected table cell.

: DrawTableFrame ( -- )
  tableheight @ tablewidth @ tabley
  tablex sp@ 1
  WinDrawGrayRectangleFrame
  2drop 2drop ;

The only thing needing explanation here is the use of sp@. This returns the address of the top of the data stack on to which we have just placed the values for the required rectangle structure. This means that we are able to use the stack as our rectangle structure without having to set memory specifically aside for this purpose. Note that the items are not lifted off the stack by the API call and hence the 2drop 2drop to remove them.

: HighlightTblItem ( -- )
\ check if the highlighted cell is in 
\ the visible range. If so then
\ highlight.
  currentrow @ toprow @ -
  dup visiblerows < swap -1 > and if
    currentcol @ currentrow @ toprow
    @ - tableptr TblSelectItem
  then ;

To highlight the active table cell simply check if the cell is currently visible on the screen and then call TblSelectItem. The toprow variable needs to be subtracted from currentrow to give the correct position of the highlighted cell on the screen.

4. Custom Cell Draw Procedure

4.1 GENERAL

When PalmOS draws the table it calls the custom cell draw procedure. Your application does not call this procedure - PalmOS calls this procedure. When PalmOS calls the custom cell draw procedure PalmOS passes the table pointer of the relevant table, the row and column of the cell to be drawn, and a Rectangle structure which describes the location and size of the cell. If you have multiple tables in your application, it is conceivable but not recommendable that you could use one custom cell draw procedure and switch on the table pointer to implement different functions for different tables. It would be way easier to write different custom cell draw procedures for different tables.

Your cell draw procedure processes the row and column arguments to determine how to populate the cell. Exactly how the row and column arguments determine the contents of the cell depends on how you choose to organize your data. PalmOS leaves a lot of room for programmer imagination.

: drawcell
  ( &bounds. col row &table. -- &bounds. col row &table. )
   installCbStack callback
\ get row column and bounds address 
\ from the stack and store
    2 pick  row ! 3 pick col ! 5 pick 5 pick 
    2dup cellbounds 2!
\ fetch y and x values
    2. d+  @a cellbounds 2@ @a 3 +
\ determine row & col convert to
\ text and display 
    buffer 0  s" r " append row @
    toprow @ + 0 <# #s #> append 
    s" , c " append col @ 0 <# #s #>
    append swap >abs WinDrawChars
\ draw grid for table cell
    drawcellgrid
  end-callback removeCbStack ;

Here we have our drawcell word to put the row and column number in the table cells and draw a grid around each cell. It works as follows. Firstly we have to use the installCbStack word from Steve Bohrer's callbacks library to fix the problems with the callback. During a callback the the items shown in the stack diagram are passed by the system so that they can be accessed by the callback word. Please note that the stack must be left exactly as it was found otherwise the callback will fail. Next we pick off the stack the row, column and the 32 bit address of the cellbounds rectangle structure, all of which are stored in variables. The following are stored in the cellbounds structure
cellbounds + 0 cells= x
cellbounds + 1 cells = y
cellbounds + 2 cells = width
cellbounds + 3 cells = height
Knowing this we can find the x and y coordinates for the cell being drawn. We then store the characters "r " at the address of buffer which is a temporary storage area which has been set aside. To this is appended the string of characters representing the number stored at row, the string ", c " and the string of characters representing the number stored at column. These are then drawn using the WinDrawChars API call. Note that we have added 3 to the x value to avoid drawing the characters too near the edge of the cell. If a grid is required then we use the drawcellgrid word to draw it and finally end the callback and use the removeCBStack from Steve Bohrer's callbacks library.

: drawcellgrid ( -- )
  cellbounds 2@ 1
  WinDrawGrayRectangleFrame ;

The drawcellgrid word is self explanatory. I should mention however that if no grid is required then leave this and the DrawTableFrame words out.

5. Scrolling the Table

Scrolling the table is easy. Of course, the user has to request a scroll, perhaps by depressing the page down or page up hardware keys, or as in our example, by tapping the on-screen scrollbar. You can catch these events using ekey and if the value of the event turns out to be sclExitEvent then do-scroll is executed.

: do-scroll ( -- )
\ Get the sclval and set = toprow
  sclpage >abs sclmax >abs sclmin
   >abs sclval >abs scrollptr 
  SclGetScrollbar sclval @ toprow ! 
  DrawTable ;

This simply gets the new values of sclpage sclmax and sclmin which are returned after the user has altered the scrollbar position. The table is then drawn using these new values.

7. Conclusion

That's all you have to do to implement a simple table. It's not as hard as it first appears and the tables are very flexible. Hopefully this very simple example will get some Quartus Forth programmers started with tables. There is another version included in the zip file called newtable. This is a version which does not use the Palm table resource although it does still require a scrollbar resource. I have used this version very successfully in some applications and offer it as an alternative. Feel free to use all this table code and modify it to suit your own purposes. Above all have fun J.

Trevor Steele
Northern Ireland
November 2000