List Viewer OPM Module for EPOC
This OPM module implements vertical list viewers for use in OPL on the EPOC machines. A list viewer is a scrollable window which displays entries of some underlying list of data records. Each record is displayed in a single row and those rows are stacked up vertically.
To identify records each record has some integer number associated with it from an interval [1..n]. Normally the list viewer displays only a subrange [i..i+k-1] with the constraint (i = 1 OR (i > 1 AND i+k-1 <= n)).
The list viewer module offers the following services:
You can create a new list viewer or close an existing one.
You can have a list viewer redrawn (e.g. when the underlying data has changed). The connection to the data is done via callbacks.
Some pointer event can be handed to the list viewer (which typically selects some record).
You can resize a list viewer or define the row size and the separation line thickness.
You may add a scrollbar which allows to reposition the list viewer in the underlying data.
You may set the top record number explicitely.
You can find out whether some record is currently visible on screen.
My implementation has the following features and misfeatures:
List viewers have a very simple interface. They are created or closed, they can be redrawn at any time and they respond to pen events.
List viewers communicate with the application through callback functions. Here each single row is drawn into a row bitmap; hence all rows are automatically clipped.
Position, width and height of a list viewer can be freely defined and redefined. It is also possible to set the height of a row, the distance between two adjacent rows and the width of the separation line.
You may associate a scrollbar to the list viewer. Any communication between scrollbar and list viewer is then done automatically.
It is a genuine OPL implementation. Hence it works on any OPL platform without resorting to OPXs and platform specific code.
The module allocates storage statically. Because at most 10 simultaneous list viewers are possible, some space is wasted when you only need a single list viewer (about 1 kByte).
There is no automatic creation of scrollbars associated with the list viewer. You will have to create your own and associate it with the list viewer. This also means e.g. that when resizing the list viewer in your program you must also resize the scrollbar.
The top line is always shown completely and starts immediately below the top border. Because the height of the list viewer does not need to be a multiple of the line width, the bottom line is only partially visible. On the last page of a list view this typically leads to an annoying empty space directly above the bottom border.
The module only provides rudimentary support for columns (by returning the relative horizontal position of a selecting mouse click).
The following steps have to be performed to make the list viewer module available:
Download the files from here.
Unpack the ZIP-archive on your PC.
Connect your EPOC device to the PC.
Install the listviewer.sis file on your EPOC machine by double clicking on it.
The files "ListViewer.omh" will be copied into "C:\System\OPL", "ListViewer.opm" and its source "ListViewer" will be copied into "C:\System\OPM", and finally the files "ListViewer_Test" and "ListViewer_Doc" into "C:\OPMDemo\ListViewer\".
If you do not have the scrollbar.opm installed, it will be done automatically.
The files "Scrollbar.omh" will be copied into "C:\System\OPL", "Scrollbar.opm" and its source "Scrollbar" will be copied into "C:\System\OPM", and finally the files "Scrollbar_Test" and "Scrollbar_Doc" into "C:\OPMDemo\Scrollbar\".
To use the list viewer module in your own program you have to do the following:
Put include lines at the beginning of your program for "Scrollbar.oph" and "ListViewer.oph" (and possibly for the OPM loader)
INCLUDE "OPM.omh" INCLUDE "Scrollbar.omh" INCLUDE "ListViewer.omh"
Load both modules and call their initialisation procedures similarly to other OPM modules:
PROC init1: LOADM OPM_loader$ OPM_loadModule:("Scrollbar") OPM_loadModule:("ListViewer") UNLOADM OPM_loader$ Scrollbar_initMODULE:("init2") ENDP PROC init2: ListViewer_initMODULE:("myFirstProcedure") ENDP ...
The string parameter for
ListViewer_initMODULE:is the name of the first procedure in your program.
Define procedures in your program to be called from the list viewer when
some record must be redrawn in the list viewer (this procedure must have one long integer parameter, one integer parameter and must not return a value),
a record is selected in the list viewer (this procedure must also have a long integer parameter, an integer parameter and must not return a value), and
the list viewer needs to know the total number of records (this procedure must have no parameters and must return a long integer value).
So whenever the list viewer must be redrawn (either by event or by call) your application model is notified and queried.
Note that the names of the procedure and the names of the parameters can be chosen freely.
Any list viewer you want to use is created by
ListViewer_create%:with the following signature:
This routine has a single parameter and returns a list viewer object identifier (which must be stored in some integer variable for later use).
The only parameter is
colourMode%which defines the depth of the window bitmap (typically it is
kGCreate4ColourMode%). A list viewer has this property fixed over its lifetime.
The routine returns a list viewer id because it is necessary to identify a list viewer in later routines (as you may have several viewers open at a time).
ListViewer_create%:does not display a list viewer yet! Additional steps are still necessary: the callbacks and the outer and row geometry have to be defined and possibly a scrollbar can be associated.
A list viewer has to be linked to a client program which takes messages about redrawal or selection and queries about the number of records. This is done via
ListViewer_linkToClient:(listViewer%, recordCountProc$, redrawRecordProc$, selectRecordProc$)
This routine has (besides the mandatory list viewer as the first parameter) several others as follows:
recordCountProc$is the name of the procedure to yield the last record number (the first is by convention always 1).
redrawRecordProc$is called by the list viewer to redraw a row showing a specific record. This routine has two parameters: the first (a long integer) is the record number to be redrawn, the second (an integer) is the number of a row bitmap into which the drawing shall be done. The client program may draw anything into the row bitmap which is later on copied into the list viewer.
selectRecordProc$is called by the list viewer module when some row for a record has been clicked. This routine has two parameters: the first (a long integer) is the record number clicked, the second (an integer) is the x-offset of the click. Normally the second parameter is not relevant but it could be when you have some column structure in your rows.
Also note that your client is responsible for handling the selections. The list viewer has no idea whether a selection may cancel another one or whether multiple selections are o.k. and so on.
The list viewer geometry is specified by calling the routine
ListViewer_setGeometry:which has the following signature:
ListViewer_setGeometry%:(listViewer%, x%, y%, width%, height%)
The first parameter is the list viewer affected.
y%give the position of the upper left corner in pixels.
width%is the list viewer"s horizontal extent and
height%its vertical extent (both dimensions in pixels). Note that the extents include the border which goes inward, i.e. that the outer size is not affected by the border width.
Hence to set up a list viewer at (200,0) with a width of 300 pixels and a height of 240 pixels you say:
LOCAL listViewer% listViewer% = ListViewer_create%:(kGCreate4ColourMode%) ListViewer_linkToClient:(listViewer%, "countRecords", "redraw", "select") ListViewer_setGeometry:(listViewer%, 200, 0, 300, 240)
ListViewer_setGeometry:does still not display a list viewer! This happens as late as possible to prevent unnecessary list viewer updates.
The list viewer so far has no idea how many vertical pixels each row will take, how far those rows are apart and how wide the horizontal lines between the rows are. This can be specified with
ListViewer_setVerticalSizes:with the following signature:
ListViewer_setVerticalSizes:(listViewer%, borderThickness%, rowHeight%, rowSkip%, separationLineHeight%)
The first parameter is the list viewer affected.
borderThickness%tells how wide the border is,
rowHeight%is the vertical size of the drawing area for a row,
rowSkip%is the vertical size of the separation area between two rows and finally
separationLineHeight%is the vertical size of the horizontal line between two rows. All dimensions are in pixels.
You may draw anything you want in the rows, but normally you will display text. Then the height of a row must be at least the font height plus the font descent, otherwise some glyphs might become clipped. It does not look to good when you directly abutt those text rows (you also would not in an editor), hence you have to define some space between the rows: this is the row skip. It is typically about 30% of the row height. Centered in this area lies the separation line. Its thickness may be 0 (then you do not see it) up to
rowSkip(then the complete separation area is black). A value of one or two pixels is typically o.k.
Based on the above parameters the vertical list viewer layout is as follows:
First comes the top border with
borderThicknessas its height.
Then comes an empty space of height
Now we have (k-1) rows where k denotes the number of completely visible rows. Each row has height
rowHeightand below a space of height
rowSkipwhich may be filled with the separation line centered in this space.
The last row has also height
rowHeight, but ideally only a space of height
Finally comes the bottom border with
borderThicknessas its height.
Hence ideally a list viewer must have a height of k×(rowHeight+rowSkip)+2×borderHeight. Practically you will not always achieve this. Then some row is partially visible to prevent annoying space at the bottom border.
As always when you set those parameters to new values, this does not take effect immediately! You have to do a redraw first (see below).
A list viewer offers no way to scroll by itself. This can easily be fixed because you can define a scrollbar whereever you want and associate it with the list viewer via
ListViewer_setScrollbar:. This routine has the following signature:
The first parameter is as always the list viewer affected.
scrollbar%is the id of a previously created scrollbar. The list viewer automatically links itself to the scrollbar as client, hence any other client linkage of the scrollbar will be lost.
Why does the list viewer not contain a scrollbar automatically? At first I had this solution but in the end it was too inflexible. Do I want the scrollbar to the left of the viewer or to the right or at the bottom? What happens when the list viewer gets resized? What happens when the scrollbar is invisible (because there is nothing to scroll)?
Hence you have to handle the scrollbar by yourself.
Whenever you fiddle around with geometry the internal data in the list viewer is changed but the visual representation is not updated! To do that you must issue a call to
which visually updates the list viewer to reflect the internal data.
The reason for this design is that you may have many updates in geometry and internal data and do a redraw only when done. Also draw is optimized to redraw only those parts which are invalid.
Feeding pointer events into list viewers is similar to feeding them into a toolbar: a centralized procedure is called in your event loop:
ListViewer_offer%:(windowID%, pointerType&, x%, y%)
The first parameter tells in which window the event has happened. The second parameter tells whether the event was a pen down, pen up or pen drag. Finally
y%give the position of the pen event. You can take all the information from the result of a call to GETEVENT32. It is identical to the information you would give to a call of
Some further remarks:
This routine will tell you as its result whether the event has been consumed by the list viewer or not.
It is not a function for a specific list viewer. The dispatch to the specific list viewer is done within the function.
The list viewer is automatically redrawn for the pointer events.
If you want to set the record at the top of the viewer by yourself (and not by receiving the correct events from the user) you may do that via
As always the first parameter is the list viewer and the second is the new top record number. Note that this routine does no update! You have to explicitely call draw or postpone that until you are done with the settings.
To check whether some record is currently on screen, you may call the routine visibleRecords which tells you about all completely visible records.
ListViewer_visibleRecords(listViewer%, topRecordNumberPtr&, bottomRecordNumberPtr&)
Note that the second and third parameters are pointers to long integers. Hence you have to call this with addresses of variables like e.g.
LOCAL low&, high& ListViewer_visibleRecords(listViewer%, ADDR(low&), ADDR(high&))
The bottom value returned sometimes seems to be off by one. This comes from the layout rules of the viewer (see above at the
setGeometryroutine). The bottom row is only considered unclipped if and only if its contents and at least a white space of
Finally to get rid of a list viewer, you have to close it as follows:
The only parameter is the list viewer identifier (just in case you have more than one list viewer open).
A sample program is included which shows how to use the list viewer module. This program is called ListViewerDemo and simulates a clent of a list viewer. It consists of three dialogs:
In the outer loop you just define how many records the model shall have. This allows you to test how the list viewer and its scrollbar behave for very few or many records. On normal completion of this dialog the next dialog is displayed.
Next you can specify in a dialog window all the information you need to set the geometry of a list viewer. On normal completion of this dialog the list viewer is resized as specified and the next dialog is displayed.
Finally you can define the vertical sizes of the list viewer and the scrollbar thickness.
After completion of that dialog you can click into the scrollbar or the list viewer window and check the effect. It is also possible to see how zooming works and to see the current visible interval by pressing the tabulator key.
Pressing Esc anytime returns you to the previous dialog or - when you are in the outer dialog- exits the program.
Conditions of use
The list viewer module is put into the public domain and is unsupported. If you think there is some bug in the program you can contact me by electronic mail. Currently I cannot promise an immediate answer or even any answer at all.
You may modify the source code but you are not allowed to publish the list viewer code as your own work when the changes are marginal. I do not like being confronted with the new OPL 5 list viewer module by Fredi Klabuster which is nearly 100% my code.
Nevertheless you may use this code freely and of course you do not have to mention me when the list viewer is part of your program.Have fun,
Thomas Tensi, August 2001
You can download the list viewer module for OPL (with source code!) for use in your own programs. A detailed instruction for installation and use is available in the SIS-file as "ListViewer_Doc". Also a tiny demo test driver is included where you can see how to practically use a list viewer.
Appendix: Change History2001-08-14 (Version 0.8)
First public version