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.
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.
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.
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.
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.
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 tables 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.
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 youre 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.
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.
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
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:
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.
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. -- )
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. -- )
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.
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. -- )
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. -- )
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.
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.
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.
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