next up previous contents index
Next: Appendices Up: XForms Online Manual Previous: An overview of all

Designing your own object classes



Earlier chapters discussed ways to build user interfaces by combining suitable objects from the Forms Library, defining a few object callbacks and using Xlib functions. However, there is always a possibility that the built-in objects of the Forms Library might not be enough. Although free objects in principle provide all the flexibility a programmer needs, there are situations where it is beneficial to create new types of objects, for example, switches or joysticks or other types of sliders, etc. In this case, a programmer can use the architecture defined by the Forms Library to create the new object class that will work smoothly with the built-in or user-created object classes.

Creating such new object classes and adding them to the library is simpler than it sounds. In fact it is almost the same as making free objects. This part gives you all the details of how to add new classes. In chapter 21 a global architectural overview is given of how the Forms Library works and how it communicates with the different object classes by means of events (messages). Chapter 22 describes in detail what type of events objects can receive and how they should react to them. Chapter 23 describes in detail the structure of the type FL_OBJECT which plays a crucial role, a role equivalent to a superclass (thus all other object classes have FL_OBJECT as their parent class) in object-oriented programming.

One of the important aspects of an object is how to draw it on the screen. Chapter 24 gives all the details on drawing objects. The Forms Library contains a large number of routines that help you draw objects. In this chapter an overview is given of all of them. Chapter 25 gives an example illustrating how to create a new object class. Due to the importance of button classes, special routines are provided by the Forms Library to facilitate the creation of this particular class of objects. Chapter 26 illustrates by two examples the procedures of creating new button classes using the special services. One of the examples is taken from the Forms Library itself and the other offers actual usability.

Sometimes it might be desirable to alter the behavior of a built-in class slightly. Obviously a full-blown (re)implementation from scratch of the original object class is not warranted. Chapter 27.1 discusses the possibilities of using the pre-emptive handler of an object to implement derived objects.

Global structure

    The Forms Library defines the basic architecture of an object class. This architecture allows different object classes developed by different programmers to work together without complications.

The Forms Library consists of a main module and a number of object class modules. The object class modules are completely independent from the main module. So new object class modules can be added without any change (nor recompilation) of the main module. The main module takes care of all the global bookkeeping and the handling of events. The object class modules have to take care of all the object specific aspects, like drawing the object, reacting to particular types of user actions, etc. For each class there exists a file that contains the object class module. For example, there are files slider.c, box.c, text.c, button.c, etc.

The main module communicates with the object class modules by means of events (messages if you prefer). Each object has to have a handle routine known to the main module so that it can be called whenever something needs to be done. One of the arguments passed to the handle routine is the type of event, e.g. FL_DRAW, indicating that the object needs to be redrawn.

Each object class consists of two components. One component, both its data and functions, is common to all object classes in the Forms Library. The other component is specific to the object class in question and is typically opaque. So for typical object classes, there should be routines provided by the object class to manipulate the object class specific data. Since C lacks inheritance as a language construct, inheritance is implemented in the Forms Library by pointers and  the global function fl_make_object().  [footnote] It is helpful to understand the global architecture and the object-oriented approach of the Forms Library, it makes reading the C code easier and also adds perspective on why some of the things are implemented the way they are.

In this chapter it is assumed that we want to create a new class with a name NEW. Creating a new object class mainly consists of writing the handle routine. There also should be a routine that adds an object of the new class to a form and associates the handle routine to it. This routine should have the following basic form:

   FL_OBJECT *fl_add_NEW(int type,FL_Coord x,FL_Coord y,
                         FL_Coord w,FL_Coord h, const char *label)

This routine must add an object of class NEW to the current form. It gets the parameters type, indicating the type of the object within the class (see below), x, y, w, and h, indicating the bounding box of the object in the current active unit (mm, point or pixels), and label which is the label of the object. This is the routine the programmer uses to add an object of class NEW to a form. See below for the precise actions this routine should take.

One of the tasks of fl_add_NEW() is to bind the event handling routine to the object. For this it will need a routine:

   static int handle_NEW(FL_OBJECT *obj,int event,FL_Coord mx,FL_Coord my,
                         int key, void *xev)

This routine is the same as the handle routine for free objects and should handle particular events for the object. mx, my give the current mouse position and key the key that was pressed (if this information is related to the event). See chapter 22 for the types of events and the actions that should be taken. xev is the XEvent that caused the invocation of the handler. Note that some of the events may have a null xev parameter, so xev should be checked before dereferencing it.

The routine should return whether the status of the object is changed, i.e., whether the event dispatcher should invoke this object's callback or if no callback whether the object is to be returned to the application program by fl_do_forms() or fl_check_forms(). What constitutes a status change is obviously dependent on the specific object class and possibly its types within this class. For example, a mouse push on a radio button is considered a status change while it is not for a normal button where a status change occurs on release.

Moreover, most classes have a number of other routines to change settings of the object or get information about it. In particular the following two routines often exist:

   void fl_set_NEW(FL_OBJECT *obj, ...)

that sets particular values for the object and

   ??? fl_get_NEW(FL_OBJECT *obj, ...)

that returns some particular information about the object. See e.g. the routines fl_set_button() and fl_get_button().

The routine fl_add_NEW()

fl_add_NEW() has to add a new object to the form and bind its handle routine to it. To make it consistent with other object classes and also more flexible, there should in fact be two routines: fl_create_NEW() that creates the object and fl_add_NEW() that actually adds it to the form. They normally look as follows:

   typedef struct { /* instance specific record */} SPEC;

   FL_OBJECT *fl_create_NEW(int type,FL_Coord x,FL_Coord y,
                            FL_Coord w,FL_Coord h,const char *label)
       FL_OBJECT *ob;

       /* create a generic object */
       ob = fl_make_object(FL_COLBOX,type,x,y,w,h,label,handle_NEW);

       /* fill in defaults */
       ob->boxtype = FL_UP_BOX;

       /* allocate instance-specific storage and fill it with defaults */
       ob->spec = fl_calloc(1, sizeof(SPEC));
       return ob;

The constant FL_NEW will indicate the object class. It should be an integer. The numbers 0-1000 and 10000 and higher are reserved for the system and should not be used. Also it is preferable to use fl_malloc(), fl_calloc(), fl_realloc() and fl_free() to allocate/free the memory for the instance specific structures as future versions of Forms Library might offer debugging/safer version of these functions. 

The pointer ob returned by fl_make_object()    will have all of its fields set to some defaults (See Chapter 23). In other words, the newly created object inherits many attributes of a generic one. Any class specific defaults that are different from the generic one can be changed after fl_make_object(). Conversion of unit,  if different from the default pixel, is performed within fl_make_object() and a class module never needs to know what the prevailing unit is. After the object is created, it has to be added to a form:

   FL_OBJECT *fl_add_NEW(int type,FL_Coord x,FL_Coord y,FL_Coord w,
                        FL_Coord h, const char *label)
       FL_OBJECT *ob;
       ob = fl_create_NEW(type,x,y,w,h,label);
       return ob;



As indicated above, the main module of the Forms Library communicates with the objects by calling the associated handling routine with, as one of the arguments, the particular event for which action must be taken. In the following we assume that obj is the object to which the event is sent. The following types of events can be sent to an object:

  The object has to be redrawn. To figure out the size of the object you can use the fields obj->x, obj->y, obj->w and obj->h. Some other aspects might also influence the way the object has to be drawn. E.g., you might want to draw the object differently when the mouse is on top of it or when the mouse is pressed on it. This can be figured out as follows. The field obj->belowmouse indicates whether the object is below the mouse. The field obj->pushed indicates whether the object is currently being pushed with the mouse. Finally, obj->focus indicate whether input focus is directed towards this object.  Note that the drawing of the object is the full responsibility of the object class. So e.g. also the bounding box and the label, which can be found in the field obj->label, have to be drawn by the object class. The Forms Library provides a large number of routines to help you draw object. See chapter 24 for more details on drawing objects and an overview of all available routines.

One caution about the draw event handle code is that all the high level routines (fl_freeze_form(), fl_deactivate_form()) should not be used. The only routines allowed are (direct) drawing and object internal book keeping routines. Attributes modifying routines, such as fl_set_object_color() etc. are not allowed.

  This event typically follows FL_DRAW and indicates the object label needs to be (re)drawn.  If the object in question always draws its label inside the bounding box, and is taken care of by FL_DRAW, you can ignore this event.

  This event is sent when the mouse has entered the bounding box. This might require some action. Note also that the field belowmouse in the object is being set. If entering only changes the appearance, redrawing the object normally suffices. Don't do this directly! Always redraw the object using the routine fl_redraw_object(). It will send an FL_DRAW event to the object but also does some other things (like setting window id's and taking care of double buffering).

  The mouse has left the bounding box. Again, normally a redraw is enough (or nothing at all).

  A motion event is sent between FL_ENTER and FL_LEAVE events when the mouse position changes on the object (in fact, it is sent all the time even if the mouse position remains the same). The mouse position is given as an argument to the handle routine.

  The user has pushed a mouse button in the object. Normally this requires some actual action. The number of the mouse button pushed is given in the key parameter. 1 = leftmouse, 2 = middlemouse, 3 = rightmouse.

  The user has released the mouse button. This event is only sent if a PUSH event was sent earlier. The number of the mouse button released is given in the key parameter. 1 = leftmouse, 2 = middelmouse, 3 = rightmouse.

  The user has pushed a mouse button twice within a certain time limit ( FL_CLICK_TIMEOUT). This event is sent after two FL_PUSH, FL_RELEASE sequence. Note that FL_DBLCLICK is only generated for objects that have non-zero obj->click_timeout fields and it will not be generated for middle mouse button clicks.

  The user has pushed a mouse button three times within a certain time window between each push. This event is sent after a FL_DBLCLICK, FL_PUSH, FL_RELEASE sequence. Set click_timeout to none zero to activate FL_TRPLCLICK.

  The mouse position has changed (while a button is down). This event is sent to an object between an FL_PUSH and an FL_RELEASE event. The mouse position is given with the routine and action can be taken. For example, sliders use this event while buttons do not.

  Input got focussed to this object. This type of event and the next two are only sent to an object for which the field obj->input is set to 1 (see below).

  Input is no longer focussed on this object.

  A key was pressed. The ASCII value (or KeySym if non-ASCII) is given with the routine. This event only happens between FL_FOCUS and FL_UNFOCUS events. Not all objects are sent keyboard events, only those that have non-zero value in field obj->input or obj->wantkey.

  A step event is sent all the time (typically 20 times a second but often less because of system delays and other time-consuming tasks, e.g. a time-consuming redraw) to an object if the field obj->automatic has been set to 1.  This can be used to make an object change appearance without user action. E.g. the clock uses these type of events.

  The user used a keyboard shortcut. The shortcut used is given in the parameter key. See below for more on shortcuts.

  This event is sent when the object is to be freed. All memory allocated by the object class should be freed when this event is received.

  Events other than the above. These events currently include ClientMessage, Selection and possibly other window manager events. All information about the event is contained in xev parameter and mx,my may or may not reflect the actual position of the mouse.

Many of these events might make it necessary that the object has to be redrawn or partially redrawn. Always do this using the routine fl_redraw_object().


      The Forms Library has a mechanism of dealing with keyboard shortcuts. In this way the user can use the keyboard rather than the mouse for particular actions. Obviously only active objects can have shortcuts. At the moment there are three object classes that use this, namely buttons, inputs and browsers although they behave differently.

The mechanism works as follows. There is a routine 


   void fl_set_object_shortcut(FL_OBJECT *obj, const char *str, int showit)

with which the object class can bind a series of keys to an object. E.g., when str is "acE#d^h" the keys a,c,E, <ALT> d and <CNTRL> h are associated with the object. The precise format is as follows: Any character in the string is considered as a shortcut, except for ^ and #, which stand for combinations with the <CONTROL>, and <ALT> key. (There is no difference between e.g. ^C and ^c.) The symbol ^ itself can be obtained using ^^. The symbol # can be obtained using ^#. So, e.g. #^# means <ALT> #. The <ESCAPE> key can be given as ^[.   

To indicate function and arrow keys, the &n sequence (n = 1 ... 35) can be used. For example, &2 indicates <F2> key.  Note that the four cursors keys (up, down, right, and left) can be given as <&A>, <&B>, <&C> and <&D> respectively.  The key & itself can be obtained by prefixing it with ^.

Parameter showit indicates whether the shortcut letter in the object label should be underlined if a match exists. Although the entire object label is searched for matches, only the first alphanumerical character in the shortcut string is used. E.g., for object label "foobar", shortcut "oO" would result in a match at the first o in "foobar" while "Oo" would not. However, "^O" always matches.

To use other special keys not described above as shortcuts, the following routine must be used


   void fl_set_object_shortcutkey(FL_OBJECT *ob, unsigned int key)

where <KEY> is an X KeySym, for example, XK_Home, XK_F1 etc. Note that function fl_set_object_shortcutkey always appends the key specified to the current shortcuts while fl_set_object_shortcuts resets the shortcuts. Of course, special keys can't be underlined.

Now whenever the user presses one of these keys an FL_SHORTCUT event is sent to the object. Here the key pressed is given with the handle routine (in the argument key). Combinations with the <ALT> key are given   by adding FL_ALT_VAL (currently the 25th bit, i.e., 0x1000000) to the ASCII value of the rest. E.g. #^E is passed as 5+FL_ALT_VAL. The object can now take action accordingly. If you use shortcuts to manipulate class object specific things, you will need to create a routine to communicate with the user, e.g., fl_set_NEW_shortcut(), and do your own internal bookkeeping to track what keys do what and then call fl_set_object_shortcut() to register the shortcut in the event dispatching module. The idea is NOT that the user himself calls fl_set_object_shortcut() but that the class provides a routine for this that also keeps track of the required internal bookkeeping. Of course, if there is no internal bookkeeping, a macro to this effect would suffice. For example, fl_set_button_shortcut is defined as fl_set_object_shortcut.

The order in which keys are handled is as follows:  First a key is tested whether any object in the form has the key as a shortcut. If affirmative,   the first of those objects gets the shortcut event. Otherwise, the key is checked to see if it is <TAB> or <RETURN>. If it is, the obj->wantkey field is checked. If the field does not contain FL_KEY_TAB bit, input is focussed on the next input field. Otherwise the key is sent to the current input field. This means that input objects only get a <TAB> or <RETURN> key sent to them if the field obj->wantkey contain FL_KEY_TAB. This is e.g. used in multi-line input fields. If the object wants all cursor keys (including <PGUP> etc.), the wantkey field can be set to FL_KEY_SPECIAL.

To summarize, the smemberobjwantkey can take on the following values or the bit-wise or of them

FL_KEY_NORMAL The default. Left and right cursor keys, <HOME> and keyEnd plus all normal keys (0-255) except for <TAB> and keyReturn.
FL_KEY_TAB FL\_KEY\_NORMAL plus <TAB>, <RETURN> and Up and Down cursor keys.
FL_KEY_SPECIAL All special keys ( > 255).
FL_KEY_ALL All keys.

It is possible for a non-input object (i.e.,obj->input is zero) to obtain special keyboard event by setting obj->wantkey to FL_KEY_SPECIAL.

The type FL_OBJECT


Each object has a number of attributes. Some of them are used by the main routine, some have a fixed meaning and should never be altered by the class routines and some are free for the class routines to use. Below we consider some of them that are likely to be used in new classes.


  This indicates the class of the object (E.g.,FL_BUTTON, FL_NEW etc.)

  This indicates the type of the object within the class. Types are integer constants that should be defined in the file NEW.h. Their use is completely free. For example, in the class slider the type is used to distinguish between horizontal and vertical sliders. At least one type should exist and the user should always provide it (just for consistency). They should be numbered from 0 upwards.

  This is the type of the bounding box for the object. The routine handle_NEW has to take care that this is actually drawn. Note that there is a routine for this, see below.

These are Coord's that indicate the bounding box of the object. They always have to be provided when adding an object. The system uses them to determine the object below the mouse. The class routines should use them to draw the object in the correct size, etc. Note that these values will change when the user resizes the form window. So never assume anything about their values but always recheck them when drawing the object.

  An integer controlling if the object should be resized if the form it is on is resized.  The options are FL_RESIZE_NONE, FL_RESIZE_X and FL_RESIZE_Y. Default is FL_RESIZE_X|FL_RESIZE_Y.

    These two variables control how the object should be placed relative to its position prior to resizing. 

    These are two color indices in the internal color lookup table. The class routines are free to use them or not. The user can provide them using the routine fl_set_object_color(). The routine fl_add_NEW() should fill in defaults.    

  This is a pointer to a text string. This can be used by class routines to provide a label for the object. The class routines can freely use this. (Don't forget allocating storage for it when you want to set it yourself, i.e., when you don't use fl_set_object_label().) The user can change it using the routine fl_set_object_label(). The label must be drawn by the routine handle_NEW when it receives a FL_DRAW event. (The system does not draw the label automatically because it does not know where to draw it.)  For non-offsetted labels, i.e., the alignment is relative to the entire bounding box, simply calling fl_draw_object_label() should be enough.

  The color of the label. The class routines can freely use this. The user sets it with fl_set_object_lcol(). 

  The size of the label. The class routines can freely use this. The user sets it with fl_set_object_lsize(). 

  The style of the label, i.e. the number of the font in which it should be drawn. The class routines can freely use this.  The user sets it with fl_set_object_lstyle().

  The alignment of the label with respect to the object. Again it is up to the class routines to do something useful with this. The possible values are FL_ALIGN_LEFT, FL_ALIGN_RIGHT, FL_ALIGN_TOP, FL_ALIGN_BOTTOM, FL_ALIGN_CENTER. FL_ALIGN_TOP_LEFT, FL_ALIGN_TOP_RIGHT, FL_ALIGN_BOTTOM_LEFT and FL_ALIGN_BOTTOM_RIGHT. The user can set this using the routine fl_set_object_align().

  An integer indicating the border width of the object. Negative indicates the up box should look ``softer'' 

  A pointer to long containing all shortcuts (as keysyms) defined for the object. (See the previous section.) You should never need them because they are fully handled by the main routines.

  This is a pointer that points to any class specific information. The fl_add_NEW() routine will have to provide storage for it. For example, for sliders it stores the minimum value, maximum value and current value of the slider. Most classes (except the most simple ones like boxes and texts) will need this. Whenever the object receives the event FL_FREEMEM it should free this memory.

  Indicates whether the object is visible. The class routines don't have to do anything with this variable. When the object is not visible the main routine will never try to draw it or send events to it. By default objects are visible. Note that a true visible does not guarantee the object is visible on the screen, for that the form need to be also visible, i.e., fl_form_is_visible() is true.    

  Indicates whether the object is active, i.e., wants to receive events other than FL_DRAW. Static objects, such as text and boxes are inactive. Changing the status should be done in the fl_add_NEW() routine if required. By default objects are active.

  Indicates whether this object can receive keyboard input. If not, events that are related to keyboard input are not sent to the object. The default input is false. It should be set by fl_add_NEW() if required. Note that not all keys are sent (see wantkey below).

  An input object normally does not receive <TAB> or <RETURN> keystrokes or any other keys except those that have values between 0-255 and left- and right-arrows (<TAB> and <RETURN> are reserved and used to switch between input objects). By setting this field to FL_KEY_TAB these keystrokes as well as as four directional cursor keys will also be sent to the object when focus is directed to it. If however, an object is only interested in keys that are special (e.g., <HOME>,<PGUP> etc), this variable can be set to FL_KEY_SPECIAL with or without input being set.

  If non-zero, it indicates the the maximum elapsed time between two mouse clicks to be considered a double click. A zero value disables double/triple click detection.

  This indicates whether this object is a radio object. This means that, whenever it is pushed, other radio objects  in the same group in the form that are pushed are released (and their pushed value is reset). Radio buttons use this. The default is false. The fl_add_NEW() routine should set it if required.

  An object is automatic if it automatically (without user actions) has to change its contents. Automatic objects get a FL_STEP event all the time. For example, the object class clock is automatic. automatic by default is false.

  This indicates whether the mouse is on this object. It is set and reset by the main routine. The class routines should never change it but can use it to draw or handle the object differently.

  This indicates whether the mouse is pushed within the bounding box of the object. It is set and reset by the main routine. Class routines should never change it but can use it to draw or handle objects differently.

  Indicates whether keyboard input is sent to this object. It is set and reset by the main routine. Never change it but you can use its value.

  This is a pointer to the interaction handling routine. fl_add_NEW() sets this by providing the correct handling routine. Normally it is never used or changed although there might be situations in which you want to change the interaction handling routine for an object, due to some user action.

      These are pointers to other objects in the form and to the form itself. They are used by the main routines. The class routines should not change them.

  A void pointer for the class routine. The main module does not reference or modify this field in any way. The object classes, including the built-in ones, may use this field.

  A long variable for the class routine. The main module does not reference or modify this field in any way. The object classes, including the built-in ones, may use this field.

  A void pointer for the application program. The main module does not reference or modify this field in any way and neither should the class routines.

  A long variable provided for the application program.

The call-back routine that the application program assigns to the object. This is the responsibility of the application program and the class routines should not use it.

The argument to the call-back routine. Again, this is the responsibility of the application program to set.

The generic object construction routine


   FL_OBJECT *fl_make_object(int objclass, int type,
                             FL_Coord x, FL_Coord y, FL_Coord w, FL_Cord h,
                             const char *label, FL_HANDLEPTR handle)

allocates a chunk of memory appropriate for all object classes and initializes the newly allocated object to the following state:

   obj->resize = FL_RESIZE_X|FL_RESIZE_Y;
   obj->nwgravity = obj->segravity = FL_NoGravity;
   obj->boxtype = FL_NO_BOX;
   obj->lcol  = FL_BLACK;
   obj->lsize = FL_DEFAULT_SIZE;   /* SMALL_SIZE, 10pt */
   obj->lstyle = FL_NORMAL_STYLE;
   obj->col1 = FL_COL1;
   obj->col2 = FL_MCOL;
   obj->wantkey == FL_KEY_NORMAL;
   obj->active = 1;
   obj->visible = 1;
   obj->bw = (borderWidth resource set ? resource_val:FL_BOUND_WIDTH);
   obj->spec = 0;

There is rarely any need for the new object class to know how the object is added to a form and how the Forms Library manages the geometry, e.g., does an object have its own window etc. Nonetheless if this information is required, use FL_ObjWin(ob) to obtain the window resource ID     the object  belongs to. Beware that an object window ID may be shared with other objects  [footnote]. Always remove an object from the screen with fl_hide_object().   

The class routine/application may reference the following members of the FL_FORM structure to obtain information on the status of the form, but should not modify them directly


int visible
indicates if the form is visible on the screen (mapped). Use fl_show_form() and/or fl_hide_form() to change this member.
int deactivated
indicates if the form is deactivated.
FL_OBJECT *focusobj
This pointer points to the object on the form that has the input focus.
FL_OBJECT *first
The first object on the form. Pointer to a linked list.
Window window
The form window.

Drawing objects


An important aspect of a new object class (or a free object) is how to draw it. As indicated above this should happen when the event FL_DRAW is received by the object. The place, i.e. bounding box, where the object has to be drawn is indicated by the fields obj->x, obj->y, obj->w obj->h. Forms are drawn in the Forms Library default visual or the user requested visual, which could be any of the X supported visuals. Hence, preferably your classes should run well in all visuals. Forms Library tries to hide as much as possible the information about graphics mode, and in general, using the built-in drawing routines is the best approach. Here are some details about graphics state in case such information is needed.

All state information is kept in a global structure of type FL_STATE and there is a total of six (6) such structures fl_state[6], each for every visual class. The structure contains the following members, among others       

XVisualInfo *xvinfo
Many properties of the current visual can be obtained from this member.

int depth
The depth of the visual. Same as what you get from xvinfo.

int vclass
The visual class, PseudoColor, TrueColor etc.

Colormap colormap
Current active colormap valid for the current visual for the entire Forms Library (except FL_CANVAS). You can allocate colors from this colormap, but you should never free it.  

Window trailblazer
This is a valid window resource ID created in the current visual with the colormap mentioned above. This member is useful if you have to call, before the form becomes active (thus don't have a window ID), some Xlib routines that require a valid window.  A macro, fl_default_window(), is defined to return this member and use of the macro is encouraged.

GC gc[16]
total of 16 GCs appropriate for the current visual and depth. The first (gc[0]) is the default GC used by many internal routines and should be modified with care. It is a good idea to use only the top 8 GCs (8-15) for your free object so that future Forms Library extensions won't interfere with your program. Since many internal drawing routines use the Forms Library's default GC (gc[0]), it can change anytime whenever drawing occurs. Therefore, if you are using this GC for some of your own drawing routines make sure to always set the proper value before using it.

Currently active visual class TrueColor, PseudoColor etc. can be obtained by the following function/macro: 


   int fl_get_form_vclass(FL_FORM *);

   int fl_get_vclass(void);

The value returned can be used as an index into the fl_state structure. Note fl_get_vclass() should only be used within a class/new object module where there can be no confusion what the ``current" form is.

Other information about the graphics mode can be obtained by using visual class as an index into the fl_state structure. For example, to print the current visual depth, code similar to the following can be used: 

   int vmode = fl_get_vclass();
   printf("depth: %d\n", fl_state[vmode].depth);

Note that fl_state[] for indices other than the currently active visual class might not be valid.

In almost all Xlib calls,  the connection to the X server and current window ID are needed. Forms Library maintains some utility functions/macros to facilitate easy utilization of Xlib calls. Since the current version of Forms Library only maintains a single connection, the global variable Display *fl_display   can be used where required. However, it is recommended that you use fl_get_display() or FL_FormDisplay(form)     instead since the function/macro version has the advantage that your program will remain compatible with future (possibly multi-connection) versions of the Forms Library.

There are a couple of ways to find out the ``current" window ID, defined as the window ID the object receiving dispatcher's messages FL_DRAW etc. belongs to. If the object ID is available, FL_ObjWin(obj) would suffice and otherwise, fl_winget() can be used. 

There are other routines that might be useful: 


   FL_FORM *fl_win_to_form(Window win)

This function takes a window ID win and returns the form the window belongs to either as an equivalent   form->window == win or as a child to form->window.

As mentioned earlier, Forms Library keeps an internal colormap initialized to predefined colors.  The predefined color symbols do not correspond to pixel values the server understands. Therefore, they should never be used in any of the GC altering or Xlib routines. To get the actual pixel value the server understands, use the following routine  


   FL_COLOR fl_get_pixel(FL_COLOR index)

e.g., to get the pixel value of red color, use

    FL_COLOR red_pixel;
    red_pixel = fl_get_pixel(FL_RED);

Or more conveniently 



This sets the foreground color in the default GC (gc[0]) to red_pixel.

To set the background color in the Forms Library's default GC, use the follow routine 


  fl_bk_color(FL_COLOR index)

To set foreground or background in GCs other than the Forms Library's default, the following functions exist:


   void fl_set_foreground(GC gc, FL_COLOR index)
   void fl_set_background(GC gc, FL_COLOR index)

which is equivalent to the following Xlib calls 


    XSetForeground(fl_display, gc, fl_get_pixel(index))
    XSetBackground(fl_display, gc, fl_get_pixel(index))

To free allocated colors from the default colormap, use the following routine


    void fl_free_colors(FL_COLOR *cols, int n);

This function frees the colors represented by the cols array.

In case the pixel values, as opposed to Forms Library's values, are known, the following routine can be used to free the colors from the default colormap


    void fl_free_pixels(unsigned long *pixels, int n);

Note that the internal colormap maintained by the Forms Library is not updated. This is in general harmless.

To modify or query the internal colormap, use the following routines,   


   long fl_mapcolor(FL_COLOR ind, int red, int green, int blue)
   long fl_mapcolorname(FL_COLOR ind, const char *name)

   void fl_getmcolor(FL_COLOR ind, int *red, int *green, int *blue)

The coordinate system of the form by default corresponds directly to the screen. Hence a pixel on the screen always has size 1 in the default coordinate system of the form.  Object coordinates are relative to the upper-right corner of the form.  

To obtain the position of the mouse in the current form/window, use the routine


   Window fl_get_form_mouse(FL_FORM *form, FL_Coord *x, FL_Coord *y, 
                            unsigned *keymask)

   Window fl_get_win_mouse(Window win, FL_Coord *x, FL_Coord *y,
                           unsigned *keymask)

The functions return the window ID the mouse is in. Upon its return, x,y would be set to the the mouse position relative to the form/window, and keymask contains information on modifier keys (same as the the corresponding XQueryPointer() argument). 

Similar routine exists that can be used to obtain the mouse location relative to the root window 


   Window fl_get_mouse(FL_Coord *x, FL_Coord *y, unsigned *keymask);

The function returns the window ID the mouse is in.

To move the mouse to a specific location relative to the root window, use the following routine


   void fl_set_mouse(FL_Coord x, FL_Coord y)

To avoid drawing outside a bounding box the following routine exists.


   void fl_set_clipping(FL_Coord x,FL_Coord y,FL_Coord w,FL_Coord h)

It sets a clipping region in the Forms Library's default GC. x, y, w and h are as in the definition of objects. Drawing is restricted to this region after the call. In this way you can prevent drawings from sticking into other objects. Always use after drawing


   void fl_unset_clipping(void)

to stop clipping.

To obtain the bounding box of an object with the dimension and location of the label taken into account (compare with fl_get_object_geometry()   the following routine exists:


   void fl_get_object_bbox(FL_OBJECT *ob, FL_Coord *x, FL_Coord *y,
                           FL_Coord *w, FL_Coord *h)

To set clippings for text, which uses a different GC, the following routine should be used


   void fl_set_text_clipping(FL_Coord x,FL_Coord y,FL_Coord w,FL_Coord h)

   void fl_unset_text_clipping(void)

For drawing text at the correct places you will need some information about the sizes of characters and strings. The following routines are provided: 


   int fl_get_char_height(int style, int size, int *ascend, int *descend)


   int fl_get_char_width(int style, int size)

These  two routines return the maximum height and width of the font used, where size indicates the point size for the font and style is the style in which the text is to be drawn. A list of valid styles can be found in Section 3.11.3. To obtain the width and height information on a specific string, use the following routines  


   int fl_get_string_width(int style, int size, const char *str, int len)

   int fl_get_string_height(int style, int size, const char *str, int len,
                            int *ascend, int *descend)

where len is the string length. The functions return the width and height of the string str respectively.

There exists also a routine that returns the width and height of a string in one call. In addition, the string passed can contain embedded newline in it and the routine will make proper adjustment so the values returned are (just) large enough to contain the multiple lines of text


   void fl_get_string_dimension(int style, int size, const char *str,
                                int len, int *width, int *height)

Sometimes, it may be useful to get the X font structure for a particular size and style as used in XForms. For this purpose, the following routine exists :


   [const] XFontStruct *fl_get_fontstruct(int style, int size)

The structure returned can be used in, say, setting the font in a particular GC

   XFontStruct *xfs = fl_get_fontstruct(FL_TIMESBOLD_STYLE, FL_HUGE_SIZE);
   XSetFont(fl_get_display(), mygc, xfs->fid);

Caller should not free the structure returned by fl_get_fontstruct().

There are a number of routines that help you draw objects on the screen. All XForms's internal drawing routine draws into the ``current window",  defined as the window the object that uses the drawing routine belongs to.  Nevertheless, the following routines can be used to set or query the current window 


   void fl_winset(Window win)
   Window fl_winget(void)

The most basic drawing routine is the rectangle routines: 


   void fl_rectf(FL_Coord x,FL_Coord y,FL_Coord w,FL_Coord h,FL_COLOR c)
   void fl_rect(FL_Coord x,FL_Coord y,FL_Coord w,FL_Coord h,FL_COLOR c)

Both draw a rectangle on the screen in color col. The difference is that fl_rectf() draws a filled rectangle while fl_rect() draws an outline.

To draw a filled rectangle with a black border, use the following routine


   void fl_rectbound(FL_Coord x,FL_Coord y,FL_Coord w,FL_Coord h,FL_COLOR c)

To draw a rectangle with rounded corners, the following routines exist 


   void fl_roundrectf(FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h,
                      FL_COLOR col)

   void fl_roundrect(FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h,
                     FL_COLOR col)

To draw a general polygon, use one of the following routines 


  typedef struct {short x,y;} FL_POINT

  void fl_polyf(FL_POINT *xpoint, int n, FL_COLOR col);

  void fl_polyl(FL_POINT *xpoint, int n, FL_COLOR col);

  void fl_polybound(FL_POINT *xpoint, int n, FL_COLOR col);

fl_polyf() draws a filled polygon; fl_polyl() draws a polyline; and fl_polybound() draws a filled polygon with a black outline. Note all polygon routines require that xpoint have spaces to hold n+1 points.

To draw an ellipse, either filled or open, the following routines can be used (use w == h to get a circle)  


   void fl_ovalf(FL_Coord x,FL_Coord y,FL_Coord w,FL_Coord h,FL_COLOR c)

   void fl_ovall(FL_Coord x,FL_Coord y,FL_Coord w,FL_Coord h,FL_COLOR c)

   void fl_ovalbound(FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h,
                      FL_COLOR c)

To draw circular arcs, either open or filled, the following routines can be used


   void fl_arc(FL_Coord x, FL_Coord y, FL_Coord radius,
               int start_theta, int end_theta, FL_COLOR col)

   void fl_arcf(FL_Coord x, FL_Coord y, FL_Coord radius,
               int thetai, int thetaf, FL_COLOR col)

where thetai and thetaf are the starting and ending angles of the arc, in unit of one tenth of a degree (1/10 degree); and x,y are the center of the arc. If theta[f] - theta[i] is larger than 3600 (360 degrees), it is truncated to 360 degrees.

To draw elliptical arcs, the following routine should be used


   void fl_pieslice(int fill, FL_Coord x, FL_Coord y, FL_Coord w,
                    FL_Coord h, int theta1, int thetaf, FL_COLOR col)

The center of the arc is the center of the bounding box specified by (x, y, w, h) and w and h specify the major and minor axes respectively. theta1 and theta2, measured in one tenth of a degree, specify the starting and ending angles measured from zero degrees (3 o'clock).

Depending on circumstance, elliptical arc may be more easily drawn using the following routine


   void fl_ovalarc(int fill, FL_Coord x, FL_Coord y, FL_Coord w,
                    FL_Coord h, int theta, int dtheta, FL_COLOR col)

Here theta specifies the starting angle, again, measured

in one tenth of a degree, relative to 3 o'clock position and dtheta specifies both the direction and extent of the arc. If dtheta is positive, it indicates counter-clockwise motion otherwise clockwise. The magnitude of dtheta is greater than 3600, it is truncated to 3600.

To connect two points with a straight line, use the following routine 


   void fl_line(FL_Coord x1, FL_Coord y1, 
                FL_Coord x2, FL_Coord y2, FL_COLOR col)

There is also a routine to draw a line along the diagonal of a box (to draw a horizontal line use h = 1 not 0.)


   void fl_diagline(FL_Coord x, FL_Coord y, FL_Coord w, FL_Coord h,
                    FL_COLOR col)

To draw multiple connected line segments, use the following routine


   typedef struct {short x, y} FL_POINT;

   void fl_lines(FL_POINT *points, int npoint, FL_COLOR col)

All coordinates in points are relative to the origin of the drawable.

To change line width or style, the following convenience functions are available  


   void fl_linewidth(int lw)
   void fl_linestyle(int style)

Use lw=0 to reset line width to the server default. Line styles can take on the following values (see XChangeGC(3X11))


Solid line. The most efficient.
Dashed line. Odd-numbered dashes are drawn in the foreground color and even-numbered dashes are not drawn, thus appearing to be transparent.
Similar to LineOnOffDash, except that the even-numbered dashes are drawn with the background color (fl_bk_color()).

It is possible to change the dash patterns of LineOnOffDash drawing request using the following routine


   void fl_dashedlinestyle(const char *dash, int ndashes)

Each element of dash is the length of a segment of the pattern in pixels. Dashed lines are drawn as alternating segments, each of an element in dash. Thus the overall length of the dash pattern, in pixels, is the sum of all elements in dash. When the pattern is used up, it repeats.

By default, all lines are drawn so they overwrite the destination pixel values. It is possible to change the drawing mode so the destination pixel values play a role in the final pixel value


   void fl_drawmode(int mode)

where the supported modes are

The default. Overwrite. Final value = Src;
Boolean exclusive-or. Useful for rubber-banding. Final value: Src xor dest.
Final value: Src and dest.
Final value: Src or dest.
Final value: ~dest.
Final value: dest.

To obtain the current settings of the line drawing attributes, use the following routines


   int fl_get_linewidth(void)

   int fl_get_linestyle(void)

   int fl_get_drawmode(void)

There are also a number of high-level drawing routines available. To draw boxes the following routine exists. Almost any object class will use it to draw the bounding box of the object. 


   void fl_drw_box(int style, FL_Coord x,FL_Coord y,FL_Coord w,FL_Coord h,
                   FL_COLOR col, int bw)

Draws a box. style is the type of the box, e.g FL_DOWN_BOX. x, y, w, and h, indicate the size of the box. c is the color. bw is the width of the boundary, which typically should be given a value obj->bw or FL_BOUND_WIDTH.   Note that a negative border width indicates a ``softer'' up box. See DEMOS/borderwidth.c for the visual effect of different border widths. 

There is another routine that draws a frame


   void fl_drw_frame(int style, FL_Coord x, FL_Coord y, 
                    FL_Coord w, FL_Coord h, FL_COLOR col, int bw)

All parameters have the usual meaning except that the frame is drawn outside of the bounding box specified.

To draw a slider of various types and shapes, use the following routine


   void fl_drw_slider(int boxtype, FL_Coord x, FL_Coord y,
                      FL_Coord w, FL_Coord h, 
                      FL_COLOR col1, FL_COLOR col2,
                      int slider_type,
                      double slider_size, double slider_value,
                      char *label, int parts, int inverted,
                      FL_Coord bw);

where slider_type is FL_VERT_SLIDER etc. See Section 16.1 for a complete list. Other parameters have the obvious meaning except for. parts, which can be one of the following

Don't draw anything.
Draw the bounding box only.
Draw the knob only.
Draw the entire slider.


For drawing text there are two routines:  


   void fl_drw_text(int align, FL_Coord x, FL_Coord y, FL_Coord w, 
                   FL_Coord h, FL_COLOR c, int style, int size, char *str)

   void fl_drw_text_beside(int align, FL_Coord x, FL_Coord y, 
                          FL_Coord w, FL_Coord h, FL_COLOR c, 
                          int style, int size, char *str)

where align is the alignment, namely, FL_ALIGN_LEFT, FL_ALIGN_CENTER etc. x, y, w and h indicate the bounding box, c is the color of the text, size is its size (in points), style is the style to be used (see Section 3.11.3 for valid styles), str is  the string itself, possibly with embedded newlines it in. fl_drw_text() draws the text inside the bounding box according to the alignment request while fl_drw_text_beside() draws the text aligned outside the box. These two routines interpret a text string starting with the character @ differently and draw some symbols instead.  Note that fl_drw_text() shrinks the bounding box by 5 pixels on all sides before computing the alignment position.

The following routine can also be used to draw text and in addition, a cursor can optionally be drawn


   void fl_drw_text_cursor(int align, FL_Coord x, FL_Coord y, 
            FL_Coord w, FL_Coord h, FL_COLOR c, int style, int size,
            char *str, int FL_COLOR ccol, int pos)

where ccol is the color of the cursor and pos is the position of the cursor (-1 means show no cursor). This routine does not interpret the meta-character @ nor does it shrink the bounding box in calculating the alignment position.

Given a bounding box and the size of an object (label or otherwise) to draw, the following routine can be used to obtain the starting position


   void fl_get_align_xy(int align, int x, int y, int w, int h,
                        int obj_xsize, int obj_ysize, 
                        int xmargin, int ymargin, int *xpos, int *ypos)

This routine works regardless if the object is to be drawn inside or outside of the bounding box specified by x,y,w and h.

For drawing object labels, the following routines might be more convenient


   void fl_draw_object_label(FL_OBJECT *ob)

   void fl_draw_object_label_outside(FL_OBJECT *ob)

These two routines assume that the alignment is relative to the full bounding box. The first routine draws the label according to the alignment, which could be inside or outside of the bounding box. The second routine will always draw the label outside of the bounding box.

An important of aspect of (re)drawing of an object is efficiency which can translate into flicker and non-responsiveness if not handled with care. For simple object like buttons or objects that do not have ``movable parts", drawing efficiency is not a serious issue although you can never be too fast. For complex objects, especially those that a user can interactively change, special care should be taken.

The most important rule for efficient redrawing is don't draw it if you don't absolutely have to, regardless how simple the drawing is. Given the networking nature of X, simple or not depends not only on the host/server speed but also the connection. What this strategy entails is that the drawing should be broken into blocks and depending on the context, draw/updates only those parts that need to be updated.

An example


Let us work through an example of how to create a simple object class colorbox. Assume we want a class with the following behavior: It should normally be red. When the user presses the mouse on it it should turn blue. When the user releases the mouse button the object should turn red again and be returned to the application program. Further, the class module should keep a total count how many times the box is pushed.

The first thing to do is to define some constants in a file colbox.h. This file should at least contain the class number and one or more types:

   #define FL_COLBOX        (FL_USER_CLASS_START+1)
			   /* 1001 <= Class number <= 9999 */

   #define FL_NORMAL_COLBOX 0     /* The only type */

Note that the type must start from zero onward.

Normally it should also contain some defaults for the boxtype and label alignment etc. The include file also has to declare all the functions available for this object class. I.e., it should contain:

   extern FL_OBJECT *fl_create_colbox(int, FL_Coord, FL_Coord, FL_Coord, 
                                      FL_Coord, const char *);
   extern FL_OBJECT *fl_add_colbox(int, FL_Coord, FL_Coord, FL_Coord, 
                                   FL_Coord, const char *);
   extern int fl_get_colorbox(FL_OBJECT *);

Secondly we have to write a module colbox.c that contains the different routines. First of all we need routines to create an object of the new type and to add it to the current form. We also need to have a counter that keeps track of number of times the colbox is pushed. They would look as follows:

   typedef struct { int counter; } SPEC;   /* no. of times pushed */

   FL_OBJECT *fl_create_colbox(int type, FL_Coord x, FL_Coord y, 
                               FL_Coord w, FL_Coord h, const char *label)
       FL_OBJECT *ob;

       /* create a generic object class with an appropriate ID */
       ob = fl_make_object(FL_COLBOX,type,x,y,w,h,label,handle_colbox);

       /* initialize some members */
       ob->col1 = FL_RED; 
       ob->col2 = FL_BLUE; 

       /* create class specific structures and initialize */
       ob->spec = fl_calloc(1, sizeof(SPEC))
       return ob;

   FL_OBJECT *fl_add_colbox(int type, FL_Coord x, FL_Coord y, 
                           FL_Coord w,  FL_Coord h, const char *label)
      FL_OBJECT *ob = fl_create_colbox(type,x,y,w,h,label);
      return ob;

The fields col1 and col2 are used to store the two colors red and blue such that the user can change them when required with the routine fl_set_object_color(). What remains is to write the handling routine handle_colbox(). It has to react to three types of events: FL_DRAW, FL_PUSH and FL_RELEASE. Also when the box is pushed, the counter should be incremented to keep a total count. Note that whether or not the mouse is pushed on the object is indicated in the field ob->pushed. Hence, when pushing and releasing the mouse the only thing that needs to be done is redrawing the object. This leads to the following piece of code:


   static int 
   handle_colbox(FL_OBJECT *ob, int event, FL_Coord mx, FL_Coord my,
                int key, void *xev)
     switch (event) {
      case FL_DRAW:
        /* Draw box */
        if (ob->pushed)
        /* fall through */
      case FL_DRAWLABEL:
        /* Draw label */
        return 0;
     case FL_PUSH:
        ((SPEC *)ob->spec)->counter++;
        return 0;
     case FL_RELEASE:
        return 1;
     case FL_FREEMEM:
        return 0;
     return 0;

That is the whole piece of code. Of course, since structure SPEC is invisible outside colbox.c, the following routine should be provided to return the total number of times the colbox is pushed:

   int fl_get_colbox(FL_OBJECT *ob)
       if(!ob || ob->objclass != FL_COLBOX)
          fprintf(stderr, "get_colbox: Bad argument or wrong type);
          return 0;
       return ((SPEC *)ob->spec)->counter;

To use it, compile it into a file colbox.o. An application program that wants to use the new object class simply should include colbox.h and link colbox.o when compiling the program. It can then use the routine fl_add_colbox() to add objects of the new type to a form.

New buttons

  Since button-like object is one of the most important, if not the most important, classes in graphical user interfaces, Forms Library provides, in addition to the ones explained earlier, a few more routines that make create new buttons or button-like objects even easier. These routines take care of the communication between the main module and the button handler so all new button classes created using this scheme behave consistently. Within this scheme, the programmer only has to write a drawing function that draws the button. There is no need to handle events or messages from the main module and all types of buttons, radio, pushed or normal are completely taken care of by the generic button class. Further, fl_get_button() and fl_set_button() work automatically without adding any code for them.

Forms Library provides two routines to facilitate the creation of new button object classes. One of the routines, fl_create_generic_button(),     can be used to create a generic button that has all the properties of a real button except that this generic button does not know what the real button looks like. The other routine, fl_add_button_class(), provide by the Forms Library can be used to register a drawing routine that completes the creation of a new button.

All button or button-like object has the following instance-specific structure, defined in forms.h, that can be used to obtain information about the current status of the button:


   typedef struct
       Pixmap pixmap;           /* for bitmap/pixmap button only        */
       Pixmap mask;             /* for bitmap/pixmap button only        */
       unsigned bits_w, bits_h; /* for bitmap/pixmap button only        */
       int val;                 /* whether pushed                       */
       int mousebut;            /* mouse button that caused the push    */
       int timdel;              /* time since last touch (TOUCH buttons)*/
       int event;               /* what event triggered the redraw      */ 
       long cspecl;             /* for non-generic class specific data  */
       void *cspec;             /* for non-generic class specific data  */
       char *file;              /* filename for the pixmap/bitmap file  */

Of all members, only val and mousebut probably will be consulted by the drawing function. cspecl and cspecv are useful for keeping track of class status other than those supported by the generic button (e.g., you might want to add a third color to a button for whatever purposes.) These two members are neither referenced nor changed by the generic button class.

Making this structure visible somewhat breaks the Forms Library's convention of hiding the instance specific data but the convenience and consistency gained by this far outweigh the compromise on data hiding.

The basic procedures in creating a new button-like object are as follows. First, just like creating any other object classes, you have to decide on a class ID, an integer between FL_USER_CLASS_START (1001) and FL_USER_CLASS_END (9999) inclusive. Then write a header file so that application programs can use this new class. The header file should include the class ID definition and function prototypes specific to this new class.

After the header file is created, you will have to write C functions that create and draw the button. Also you need an interface routine to place the newly created button onto a form.

After creating the generic button, the new button class should be made known to the button driver via the following function


    void fl_add_button_class(int objclass,
                             void (*draw)(FL_OBJECT *ob),
                             void (*cleanup)(FL_BUTTON_SPEC *));

where objclass is the class ID, and draw is a function that will be called to draw the button and cleanup is a function that will be called prior to destroying the button. You need a clean-up function only if the drawing routine uses cspecv field of FL_BUTTON_SPEC to hold dynamic memory allocated by the new button.

We use two examples to show how new buttons are created. The first example is taken from the button class in the Forms Library, that is, real working source code that implements the button class. To illustrate the entire process of creating this class, let us call this button class FL_NBUTTON.

First we create a header file to be included in an application program that uses this button class:

   #ifndef NBUTTON_H
   #define NBUTTON_H

   #define FL_NBUTTON     1005

   extern FL_OBJECT *fl_create_nbutton(int, FL_Coord, FL_Coord,
                                    FL_Coord, FL_Coord, const char *);
   extern FL_OBJECT *fl_add_nbutton(int, FL_Coord, FL_Coord,
                                    FL_Coord, FL_Coord, const char *);

Now the drawing function. We use obj->col1 for the normal color of the box; obj->col2 for the color of the box when pushed. We also add an extra property that when mouse moves over the button box, it changes color. The following is the full source code that implements this:  



   static void draw_nbutton(FL_OBJECT * ob)
       long col;

       /* box color. If pushed we use ob->col2, otherwise use ob->col1 */
       col = ((SPEC *) (ob->spec))->val ? ob->col2 : ob->col1;
       /* if mouse is on top of the button, we change the color of
        * the button to a different color. However we only do this if the
        * box has the default color.
       if (ob->belowmouse && col == FL_COL1)
           col = FL_MCOL;
       /* If original button is an up_box and it is being pushed,
        * we draw a down_box. Otherwise, don't have to change
        * the boxtype
       if (ob->boxtype == FL_UP_BOX && ((SPEC *) (ob->spec))->val)
          fl_drw_box(FL_DOWN_BOX, ob->x, ob->y, ob->w, ob->h, col, ob->bw);
          fl_drw_box(ob->boxtype, ob->x, ob->y, ob->w, ob->h, col, ob->bw);
       /* draw the button label */
       /* if the button is a return button, draw the return symbol */
       if (ob->type == FL_RETURN_BUTTON)
          fl_drw_text(FL_ALIGN_CENTER, (ob->x + ob->w - 0.8 * ob->h - 1),
                     (ob->y + 0.2 * ob->h), (0.6 * ob->h),
                     (0.6 * ob->h),ob->lcol,0,0,"@returnarrow");

Since we don't use cspecv field, we don't have to write a clean-up function.

Next, following the standard procedures of the Forms Library, we code a separate routine that creates the new button

   FL_OBJECT *fl_create_nbutton(int type, FL_Coord x, FL_Coord y,
                              FL_Coord w, FL_Coord h, const char *label)
       FL_OBJECT *ob;
       ob = fl_create_generic_button(FL_NBUTTON, type, x, y,w,h, label);
       fl_add_button_class(FL_NBUTTON, draw_nbutton, 0);
       ob->col1 = FL_COL1;          /* normal color           */
       ob->col2 = FL_MCOL;          /* pushed color           */
       ob->align = FL_ALIGN_CENTER; /* button label placement */

You will also need a routine that adds the newly created button to a form

   FL_OBJECT *fl_add_nbutton(int type, FL_Coord x, FL_Coord y,
                             FL_Coord w, FL_Coord h, const char *label)
       FL_OBJECT *ob = fl_create_nbutton(type,x,y,w,h,label);
       fl_add_object(fl_current_form, ob);
       return ob;

This concludes the creation of button class FL_NBUTTON.

The next example implements a button that might be added to the Forms Library in the future. We call this button crossbutton. Normally this button shows a small up box with a label on the right. When pushed, the up box becomes a down box and a small cross appears on top of it. This kind of button obviously is best used as a push button or a radio button. However, the Forms Library does not enforce this. It can be enforced, however, by the application program or by object class developers.



Figure 26.1: New button class

We choose to use the ob->col1 as the color of the box and ob->col2 as the color of the cross (remember these two colors are changeable by the application program via fl_set_object_color()). Note this decision on color use is somewhat arbitrary, we can easily make ob->col2 to be the color of the button when pushed and use ob->spec->cspecl for the cross color (another routine fl_set_crossbutton_crosscol(FL_OBJECT *, FL_COLOR) should be provided to change the cross color in this case).

We start by defining the class ID and declaring the utility routine prototypes in the header file (crossbut.h):

   #ifndef CROSSBUTTON_H
   #define CROSSBUTTON_H

   #define FL_CROSSBUTTON      2000

   extern FL_OBJECT *fl_add_crossbutton(int, FL_Coord, FL_Coord, 
                            FL_Coord, FL_Coord, const char *);

   extern FL_OBJECT *fl_create_crossbutton(int, FL_Coord, FL_Coord,
                                       FL_Coord, FL_Coord, const char *);

Next we write the actual code that implements crossbutton class crossbut.c:  

    * routines implementing the "crossbutton" class
   #include "forms.h"
   #include "crossbut.h"


   /** How to draw it */
   static void draw_crossbutton(FL_OBJECT *ob)
       FL_Coord xx, yy, ww, hh;
       SPEC *sp = ob->spec;

       /* there is no visual change when mouse enters/leaves the box */
       if(sp->event == FL_ENTER || sp->event == FL_LEAVE)

       /* draw the bounding box first */
       fl_drw_box(ob->boxtype, ob->x, ob->y, ob->w, ob->h, ob->col1, ob->bw);

       /* draw the box that contains the cross */
       ww = hh = (0.5 * FL_min(ob->w, ob->h))  -1;
       xx = ob->x + FL_abs(ob->bw);
       yy = ob->y + (ob->h - hh)/2;

       /* if pushed, draw a down box with the cross */
       if(((SPEC *)ob->spec)->val)
         fl_drw_text(FL_ALIGN_CENTER, xx-2, yy-2, ww+4, hh+4, ob->col2, 
		     0, 0, "@9plus");
         fl_drw_box(FL_UP_BOX,xx,yy,ww,hh,ob->col1, ob->bw);

       /* label */
       if (ob->align == FL_ALIGN_CENTER)
           fl_drw_text(FL_ALIGN_LEFT, xx + ww + 2, ob->y, 0, ob->h,
                       ob->lcol, ob->lstyle, ob->lsize, ob->label);

       if (ob->type == FL_RETURN_BUTTON)
                   (FL_Coord)(ob->x + ob->w - 0.8 * ob->h),
                   (FL_Coord)(ob->y + 0.2 * ob->h),
                   (FL_Coord)(0.6 * ob->h),
                   (FL_Coord)(0.6 * ob->h),ob->lcol,0,0,"@returnarrow");

This button class is somewhat different from the normal button class (FL_BUTTON) in that we enforce the appearance of a crossbutton so that an un-pushed crossbutton always has an upbox and a pushed one always has a downbox. Note that the box that contains the cross is not the bounding box of a crossbutton although it can be if the drawing function is coded so.

Rest of the code simply takes care of interfaces:

   /* creation routine */
   fl_create_crossbutton(int type, FL_Coord x, FL_Coord y, FL_Coord w,
                         FL_Coord h, const char *label)
        FL_OBJECT *ob;
        fl_add_button_class(FL_CROSSBUTTON, draw_crossbutton, 0);

        /* if you want to make cross button only available for
         * push or radio buttons, do it here as follows: 
           if(type != FL_PUSH_BUTTON && type != FL_RADIO_BUTTON)
              type = FL_PUSH_BUTTON;
        ob = fl_create_generic_button(FL_CROSSBUTTON,type,x,y,w,h,label);
        ob->boxtype = FL_NO_BOX;
        ob->col2 = FL_BLACK;   /* cross color */
        return ob;

   /* interface routine to add a crossbutton to a form */
   fl_add_crossbutton(int type, FL_Coord x, FL_Coord y, FL_Coord w,
                      FL_Coord h, const char *label)
       FL_OBJECT *ob = fl_create_crossbutton(type, x, y, w, h, label);
       fl_add_object(fl_current_form, ob);
       return ob;

The actual code is in DEMOS/crossbut.c and DEMOS/crossbut.h. An application program only needs to #include the header file crossbut.h and link with crossbut.o to use this new object class. There is no need to change or re-compile the Forms Library. Of course, if you really like the new object class, you can modify the system header file forms.h to include your new class header file automatically (either through inclusion at compile time or include the actual header). You can also place the object file (crossbut.o) in libforms.a if you wish. Note however, library so created may not be distributed.

Since the current version of Form Designer does not support any new object classes developed as outlined above, the best approach is to use another object class as stubs when creating a form, for example, you might want to use checkbutton as stubs for crossbutton. Once the position and size are satisfactory, generate the C-code and then manually change checkbutton to crossbutton. You probably can automate this with some scripts.

Finally there is a demo program utilizing this new button class. The program is in newbutton.c. 

Using a pre-emptive handler

Pre-emptive handlers come into being due to reasons not related to developing new classes. They are provided for the application programs to have access to the current state or event of a particular object. However, with some care, this preemptive handler can be used to override parts of the original built-in handler thus yielding a new class of objects.

The Pre-emptive and Post Object Handler


As mentioned earlier, an object module communicates with the main module via events and the agent is the event handler, which determines how an object responds to various events such as a mouse click or a key press. A pre-emptive handler is a handler which, if installed, gets called first by the main module when an event for the object occurs. The pre-emptive handler has the option to override the built-in handler by informing the main module not to call the built-in handler, thus altering the behavior of the built-in objects. The post handler, on the other hand, is called when the object handler has finished its tasks and thus does not offer the capability of overriding the built-in handler. It is much safer, however.

The API to install a pre- or post-handler for an object is as follows


    typedef int (*FL_HANDLEPTR)(FL_OBJECT *ob, int event,
                                FL_Coord mx, FL_Coord my,
                                int key, void *raw_event);

    void fl_set_object_prehandler(FL_OBJECT *ob, FL_HANDLEPTR phandler);
    void fl_set_object_posthandler(FL_OBJECT *ob, FL_HANDLEPTR phandler);

Where event is the generic event in the Forms Library, that is, FL_DRAW, FL_ENTER etc. Parameter mx, my are the mouse position and key is the key pressed. The last parameter raw_event is the (cast) XEvent that caused the invocation of the pre- or post-handler. Again, not all FL event has corresponding xev and any dereferencing of xev should only be done after making sure it is not null.

Notice that the pre- and post-handler have the same function prototype as the built-in handler. Actually they are called with exactly the same parameters by the event dispatcher. The prehandler should return !FL_PREEMPT if the processing by the built-in handler should continue. A return value of FL_PREEMPT  will prevent the dispatcher from calling the built-in handler. The post-handler is free to return anything and the return value is not used. Note that a post-handler will receive all events even if the object the post-handler is registered for does not. For example, a post-handler for a box (a static object that only receives FL_DRAW) receives all events.

See demo program preemptive.c and xyplotall.c for examples.

Bear in mind that modifying the built-in behavior is in general not a good idea. Using the pre-emptive handler for the purpose of ``peeking", however, is quite legitimate and can be useful in some situations.

next up previous contents index
Next: Appendices Up: XForms Online Manual Previous: An overview of all

© 1996 1997 Danny Uy,DropDead, Inc.