C for Yourself - part 59

Redrawing windows and plotting fonts in practice - Steve Mumford walks you through

Over the last few columns, we've looked at a variety of RISC OS programming techniques including font manipulation and data storage. This month, I've put a few of those ideas into practice, and an example application should be included on the cover disc. It's fairly simple in operation; the program installs an icon on the icon bar and allows you to open a window, initially blank.

Clicking with the Select button in the window inserts a short string of text in Trinity.Medium, giving the window-relative x and y coordinates of the bottom left hand corner of the text object. Clicking on an object that already exists changes its colour. Window refreshing is partially optimised, in that only text objects that have been obscured are painted back onto the screen during a redraw operation. Finally, mode changes are recognised and dealt with accordingly; font handles are reassigned at the correct resolution so that the aspect ratio of the text doesn't alter.

There are several features of the example application that are worth looking at - firstly, I've collected some of the important program variables into one structure, which just keeps them in one place and has the advantage that you don't have to hunt through the source code to make any fundamental changes.

typedef struct
{
  int RISCOS_Version;
  int Task_Handle;
  int Iconbar_Icon_Handle;
  int Quit_Flag;
  int Current_Menu_Flag;
  int Printing_Flag;
  char Application_Name[255];
  char AppFilePath_Name[255];
  char AppSprite_Name[255];
} ProgramVariables;

I've also created structures that hold all the information we're likely to need to be able to display a line of characters in a chosen font - the first structure, FontObject, holds the details for just one string, and the second, FontObject_List, allows us to hold a number of those objects in the form of a linked list. For the sake of simplicity, these font objects are limited in the length of text they can store, but of course with more complicated constructor routines, this limitation can be removed.

typedef struct
{
  int Font_Size;
  int Font_Resolution;
  int Font_Flags;
  int Font_Handle;
  unsigned long int bg_colour;
  unsigned long int fg_colour;
  int x;
  int y;
  int x_min;
  int y_min;
  int x_ext;
  int y_ext;
  int Selected_Flag;
  int LabelID_Flag;
  char Font_Name[255];
  char Text[255];
} FontObject;

typedef struct FontObject List
{
  FontObject FontObj;
  struct FontObject_List *next;
} FontObject_List;

The start of the linked list is pointed to by the variable FontObj_List_Root, and any additions are inserted at the start of the list - to make the process easier, the function add_fontobject_item() automates a couple of procedures, including the acquisition of an initial font handle and an approximation of the bounding box of the string.

The latter was calculated using the SWI Font_StringBBox - this is by no means an ideal method as rounding errors can creep in, but it demonstrates the general idea quite nicely. The only thing you have to remember is that the coordinates are returned in millipoints, so the SWI Font_ConverttoOS comes in handy - the following chunk of code finds the minimum x and y coordinates of the bounding box relative to its plotting origin and converts them to OS units:

au_selectfont(newobject_ptr->FontObj.Font_Handle);

in.r[1] = (int) newobject_ptr->FontObj.Text;
_kernel_swi(Font_StringBBox, &in, &out);

newobject_ptr->FontObj.x_min = out.r[1];
newobject_ptr->FontObj.y_min = out.r[2];

...

in.r[1] = newobject_ptr->FontObj.x_min;
in.r[2] = newobject_ptr->FontObj.y_min;
_kernel_swi(Font_ConverttoOS, &in, &out);

newobject_ptr->FontObj.x_min = out.r[1];
newobject_ptr->FontObj.y_min = out.r[2];

These bounding boxes are used during the window refresh cycle to minimise the amount of work necessary, by determining whether a particular text object lies wholly or partially within the rectangle being redrawn - re-plotting all of the objects every redraw can slow the application down dramatically.

During this process, we need to translate between window workarea coordinates and absolute screen positions, and although the details we need are readily available during the redraw loop, we have to do a little more work outside it if we want to get our hands on the same information. The Wimp_GetWindowState function allows you to determine the window coordinates and dimensions - the following code is used within the routine that handles mouse clicks within the application's main window:

unsigned char temp_block[255];
_kernel_swi_regs in, out;

au_wordtobyte(win_data[0].win_handle, temp_block, 0);

in.r[1] = (int) temp_block;
_kernel_swi(Wimp_GetWindowState, &in, &out);

visx_min = au_bytetoword(temp_block, 4);
visy_max = au_bytetoword(temp_block, 16);
scroll_x = au_bytetoword(temp_block, 20);
scroll_y = au_bytetoword(temp_block, 24);

There's another aspect of C that I'd like to cover next month, namely calling functions by pointer. In the same way that you can reference the start of an array by a pointer, you can also store the start address of a function as a pointer. This allows us to perform a few useful tricks that will enable us to create a stronger application skeleton while retaining enough flexibility to allow custom-written handlers to be added to the core of the program. We'll be looking at these next time round - hope to see you then.


Source: Acorn User - 205 - March 1999
Publication: Acorn User
Contributor: Steve Mumford