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 is

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

which 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(), discussed below, can be used to register a drawing routine that completes the creation of a new button.

All button or button-like objects have 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 int   bits_w,   /* for bitmap/pixmap button only */
                        bits_h;
         int            val;      /* whether it's 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 */
     } FL_BUTTON_STRUCT;

Of all its 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 outweights 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. You also will 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 *), void
     (*cleanup)(FL_BUTTON_SPEC *));

where objclass is the class ID, and draw is a function that will be called to draw the button. cleanup is a function that will be called prior to destroying the button. You need a cleanup function only if the drawing routine uses the cspecv field of FL_BUTTON_STRUCT to hold memory allocated dynamically 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, i.e., its 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  FL_USER_CLASS_START

     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 *);

     #endif

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

     static void draw_nbutton(FL_OBJECT *obj) {
         FL_COLOR col;

         /* box color. If pushed we use obj->col2, otherwise use obj->col1 */
         col = ((FL_BUTTON_STRUCT *) obj->spec)->val ?
               obj->col2 : obj->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 (obj->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 (   obj->boxtype == FL_UP_BOX
              && ((FL_BUTTON_STRUCT *) obj->spec)->val)
              fl_drw_box(FL_DOWN_BOX, obj->x, obj->y, obj->w, obj->h,
                         col, obj->bw);
          else
              fl_drw_box(obj->boxtype, obj->x, obj->y, obj->w, obj->h,
                         col, obj->bw);

          /* draw the button label */
          fl_drw_object_label(obj);

          /* if the button is a return button, draw the return symbol.
           * Note that size and style are 0 as they are not used when
           * drawing symbols */
          if (obj->type == FL_RETURN_BUTTON)
              fl_drw_text(FL_ALIGN_CENTER,
                          obj->x + obj->w - 0.8 * obj->h - 1,
                          obj->y + 0.2 * obj->h, 0.6 * obj->h,
                          0.6 * obj->h, obj->lcol, 0, 0, "@returnarrow");
     }

Note that when drawing symbols, the style and size are irrelevent and set to zero in fl_drw_text() above.

Since we don't use the cspecv field to point to dynamically allocated memory 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 (A separate creation routine is useful for integration into the Form Designer.)

     FL_OBJECT *fl_create_nbutton(int type, FL_Coord x, FL_Coord y,
                                  FL_Coord w, FL_Coord h,
                                  const char *label) {
         FL_OBJECT *obj;

         obj = fl_create_generic_button(FL_NBUTTON, type, x, y, w, h, label);
         fl_add_button_class(FL_NBUTTON, draw_nbutton, NULL);

         obj->col1  = FL_COL1;          /* normal color */
         obj->col2  = FL_MCOL;          /* pushed color */
         obj->align = FL_ALIGN_CENTER;  /* button label placement */

         return obj;
     }

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 *obj = fl_create_nbutton(type, x, y, w, h, label);

         fl_add_object(fl_current_form, obj);
         return obj;
     }

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 a 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 the object class developers.

We choose to use obj->col1 as the color of the box and obj->col2 as the color of the cross (remember these two colors are changeable by the application program via fl_set_object_color()). Note that this decision on color use is somewhat arbitrary, we could have easily made obj->col2 the color of the button when pushed and use obj->spec->cspecl for the cross color (another routine named e.g., fl_set_crossbutton_crosscol() 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 (FL_USER_CLASS_START + 2)

     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 *);
     #endif

Next we write the actual code that implements crossbutton class and put it into crossbut.c:

     /* routines implementing the "crossbutton" class */

     #include <forms.h>
     #include "crossbut.h"

     /** How to draw it */

     static void draw_crossbutton(FL_OBJECT *obj) {
         FL_Coord xx, yy, ww, hh;
         FL_BUTTON_STRUCT *sp = obj->spec;

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

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

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

         /* If pushed, draw a down box with the cross */
         if (sp->val) {
             fl_drw_box(FL_DOWN_BOX, xx, yy, ww, hh, obj->col1, obj->bw);
             fl_drw_text(FL_ALIGN_CENTER, xx - 2, yy - 2, ww + 4, hh + 4,
                         obj->col2, 0, 0, "@9plus");
         } else
             fl_drw_box(FL_UP_BOX, xx, yy, ww, hh, obj->col1, obj->bw);

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

         if (obj->type == FL_RETURN_BUTTON)
             fl_drw_text(FL_ALIGN_CENTER, obj->x + obj->w - 0.8 * obj->h,
                         obj->y + 0.2 * obj->h, 0.6 * obj->h, 0.6 * obj->h,
                         obj->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.

The rest of the code simply takes care of interfaces:

     /* creation routine */

     FL_OBJECT * fl_create_crossbutton(int type, FL_Coord x, FL_Coord y,
                                       FL_Coord w, FL_Coord h,
                                       const char *label) {
         FL_OBJECT *obj;

         fl_add_button_class(FL_CROSSBUTTON, draw_crossbutton, NULL);

         /* 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;
          */

          obj = fl_create_generic_button(FL_CROSSBUTTON, type, x, y, w, h,
                                         label);
          obj->boxtype = FL_NO_BOX;
          obj->col2 = FL_BLACK; /* cross color */

          return obj;
     }

     /* interface routine to add a crossbutton to a form */

     FL_OBJECT *fl_add_crossbutton(int type, FL_Coord x, FL_Coord y,
                                   FL_Coord w, FL_Coord h,
                                   const char *label) {
        FL_OBJECT *obj = fl_create_crossbutton(type, x, y, w, h, label);

        fl_add_object(fl_current_form, obj);
        return obj;
     }

The actual code is in the demo directory, see the files crossbut.c and 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 by including the actual header). You can also place the object file (crossbut.o) in libforms.a and libforms.so if you wish. Note however that this will make your application programs dependent on your personal version of the library.

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 the 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 newbutton.c.