Programmers' guide to graph package internals

Overview

This document describes various aspects to graph package internals. It is intended to be updated each time some new bit of design is done, some new facility is added, or some old part is better documented. It is meant to be a place where the graph package developer can go and look up how/why bits of the graph package are the way they are. Each design section should have a separate header and be cross referenced to other sections where appropriate.

The busy cursor

When windows are displayed on the screen we want to be able to tell the user that the application is busy and they should not try to interact with it. The easiest way to do this is to change the mouse pointer (aka 'cursor') to a watch shape, this has become a de-facto standard for saying the application is busy. The original graph package interface was through a single call, graphBusyCursorAll, which was intended to display a busy cursor on all acedb windows. The intention was that the application code or the graph package could show this cursor when required.

To avoid the application having to know when to turn cursors on/off the busy cursors were shown every time an Xevent was dispatched, the intention being that an event dispatch would often mean some significant work was being done by the application. This method had two major flaws:

  1. It meant a large number of extra requests to the server every time an event was dispatched, whether that event resulted in significant work or not.
  2. It did not work properly in that any new window which took a long time to construct would not have a busy cursor because no event processing (and hence no cursor setting) could be done while the window was being constructed.
The excess number of extra requests often resulted in annoying cursor flashing when links between X server & client were slow.

To get round this problem the way cursors are set on/off was changed.

The busy cursor implementation

We would like busy cursors to be shown whenever the application has sufficient work to do that it cannot respond to the users interactions. In acedb graphical applications this includes: The obvious places to put busy cursor calls are in the graph creation, redraw, resize routines and in the set of graph routines that deal with events as these routines are the ones that call the applications callback routines. There are several problems with doing this:
  1. unfortunately there is no one 'layer' of the graph package that contains these calls
  2. more seriously, some of the routines that respond to events then set up another layer of callbacks which will then go on the do the serious work, e.g. the menu routines set up callbacks that will then call the applications callback routines once the user has selected an item from the menu.
  3. sometimes routines start an action that will only be completed some time later by a registered callback (this happens with graph creation).
  4. some application callbacks or graph callbacks are called often and for a very short time so setting a busy cursor would not be appropriate, e.g. in text entry boxes.
Fortunately these problems can be solved in a reasonably satisfactory way, the following sections detail what was done.

Graph layers and where to put the calls

Places where busy cursors must be set on/off are restricted to just a few places: busy cursor calls were added to:

w2/graphcon.c:  graphCreate
w2/graphsub.c:  gLeftDown, gMiddleDown, 
w2/graphxlib.c: graphClipDraw
w2/graphxt.c:   resizeCall, mouseCall, keyboardCall
w2/xtsubs.c:    freeMenuSelect, oldMenuSelect
Although calls have had to be added at conceptually 3 layers, at least the number of calls is small (11 on/off pairs of busy cursor setting). This exercise reveals that the layering in the graph package has broken down, there is no fixed layer that just contains graph interface calls, the graph interface calls are embedded throughout the graph code.

Graph creation

Whenever a new graph is built this happens in several phases:
  1. create graph structures and display a blank window
  2. call an application callback to stack up drawing requests to the window
  3. call the graph redisplay function to actually draw into the window
We want the user to see the cursor go on at the beginning of the phases and go off at the end. In this case we are lucky because all new windows are created by one routine and are drawn by one routine, we can thus turn on the busy cursor in graphCreate and then turn it off again in graphClipDraw. In fact in graphClipDraw the busy cursor is turned and then turned off but no harm is done by turning on the busy cursor twice.

Graph resize/redisplay

Both resize and redisplay happen in two phases:
  1. call an application callback to either recalculate and stack up drawing requests to the window (for a resize), or to do any updating for redisplay
  2. call the graph redisplay/resize function to actually size/draw into the window
These activties happen in just two routines: resizeCall, graphClipDraw. In fact resizeCall does not do any drawing, it leaves that to graphClipDraw, resizing is just a special case of redrawing.

Setting busy cursors for application callbacks

At first site the obvious place is to put calls in the all of the graph routines that are callback routines for windows (generally called nnnnnCall and mostly defined in graphxt.c, see the translation tables). But this is not always correct because these routines sometimes just register routines to be called later so showing a busy cursor would be inappropriate: The solution to finding where to put the busy cursor calls was to look for where the application callbacks where actually called from rather than in the graph callbacks themselves. This was easy to do because the application callbacks are all kept in an array called func which is part of the graph structure. Searching through the code produced a very places where the application callbacks were invoked which all looked like this:

        (*gActive->func[*****])() ;
All calls like this were bracketed with the calls to turn on/off the busy cursor, e.g.

  if (isResize && gActive->func[RESIZE])
    {
    graphBusyCursorAll (TRUE);

    (*gActive->func[RESIZE])() ;

    graphBusyCursorAll (FALSE) ;
    }

Dealing with application callbacks that don't need a busy cursor

Some application and graph callbacks should not have a busy cursor associated with them, a good example of this is keyboardCall in graphxt.c. We don't really want a busy cursor to shown for every key press, e.g. just typing letters, so no busy cursor should be set in this routine for these key presses. Unfortunately it is not enough to just not do the busy cursor call here because every key press also causes an expose handler to be called, the expose handler calls the redraw code which correctly shows the busy cursor !! To avoid polluting the redraw routine, I've added a call to the busy cursor routines that will make the normal busy cursor call do nothing. This should be used with care and in paired calls to start/stop disabling (as below) otherwise the busy cursor stuff will get completely screwed up. This allows us to selectively stop the normal busy cursor calls from doing anything when needs be:

  if (kval && gActive->func[KEYBOARD])
    {
    if (((keysym >= XK_KP_Space) && (keysym <= XK_KP_9))    /* Keypad keys */
	|| ((keysym >= XK_space) && (keysym <= XK_asciitilde)) /* normal keyboard keys */
	|| keysym == XK_BackSpace || keysym == XK_Delete    /* delete keys */
	|| keysym == XK_Left || keysym == XK_Right)	    /* left/right arrow keys */
      graphSetBusyCursorInactive(TRUE) ;

    graphBusyCursorAll (TRUE);

    (*gActive->func[KEYBOARD])(kval, modifier) ;

    graphBusyCursorAll (FALSE) ;

    graphSetBusyCursorInactive(FALSE) ;
    }
A caveat to the above code is that the above works because the expose handler is called in between the cursor setting calls, this may not happen for other sections of code and may not even be guaranteed to happen here, careful experimentation is required.

A more serious caveat to the current implementation of selective busy cursor setting in the keyboard callback is that I have done this generally for ALL keyboard input, assuming that no other part of the code has done something like set a translation for a common letter such as "q" that launches some long running bit of code. If this happened the application would be locked up with a busy cursor. It would be possible to add the busy cursor calls to the text entry routines but this is not nearly so clean.

Summary

The busy cursor is now only set on and off when the application is doing significant work, it is not set on/off repeatedly as it used to be. Text entry/deletion is now possible without the flickering cursor. The busy cursor calls have only had to be added to the graph package in about 10 places and this is entirely internal to the graph package thus maintaining the system that the application code need not be responsible for this.