CHAPTER 7: Examples


7.1 Overview

This chapter describes four complete examples of applications using C API actions. The files containing these applications are in the directory $EMPRESSPATH/rdbms/gui/examples. These applications are:


Application Name Description
SimpleApp A simple application using mx routines to access a database. It accesses text fields in the main window.
Images_c_api Illustrates the use of image fields, toggle buttons, scales, and multiple windows. It uses mr routines.
ListApp Uses embedded SQL. Illustrates the use of multilist, and multiple modules.
ExamResults Illustrates the use of a user object as a drawing area for displaying a bar chart. Uses embedded SQL.
FlatFillApp Reads a flat file and displays the data in a multilist in Empress GUI Builder.



7.2 Creating the Example Database

In the $EMPRESSPATH/rdbms/gui/examples directory, the file mkexdb is a script file which creates a GUI database and imports the example applications and data tables.

Run the script as follows:

$EMPRESSPATH/rdbms/gui/examples/mkexdb

It will create a database called examp_db in your current directory.



7.3 Compiling and Running the Examples

In the $EMPRESSPATH/rdbms/gui/examples directory, the file mkexgui is a script file which compiles the C files to executable. It contains the following statements:

: '(c) Copyright Empress Software Inc. 1983, 2006'

case "$EMPRESSPATH" in
"")
        EMPRESSPATH='cd ../..; pwd' ;;
esac

EXDIR=${EMPRESSPATH}/rdbms/gui/examples

. $EMPRESSPATH/rdbms/util_bin/banner

$$EMPRESSPATH/bin/empesql ${EXDIR}/ex3_actn.c ex3_actp.c
$EMPRESSPATH/bin/empesql ${EXDIR}/ex4_actn.c ex4_actp.c

$EMPRESSPATH/bin/empguicc -ESQL -o ex_gui ${EXDIR}/ex1_actn.c \
    ${EXDIR}/ex2_actn.c ex3_actp.c ex4_actp.c ${EXDIR}/ex5_actn.c
    ${EXDIR}/ex_actab.c

rm -f ex3_actp.c ex4_actp.c ex1_actn.o ex2_actn.o ex3_actp.o ex4_actp.o \
    ex5_actn.o ex_actab.o

Note that ex3_actn.c and ex3_actn.c have to be precompiled with the Empress SQL C Precompiler, and that the final linking requires the -ESQL flag to indicate that the embedded SQL libraries should be included.

Run the script to create an executable named ex_gui :

$EMPRESSPATH/rdbms/gui/examples/mkexgui
Run your ex_gui executable, with the database and the application name as arguments. For example, to execute the first application, SimpleApp:
ex_gui examp_db SimpleApp



7.4 The ex_actab.c File

In the ex_actab.c file, the user_action_table array contains entries which specify the action table associated with each module. These action tables are declared as extern, since they are defined in other files.

#include <guicc.h>

extern action_tab_entry     msgui_public_action_table[];
extern action_tab_entry     SimpleApp_action_table[];
extern action_tab_entry     Images_action_table[];
extern action_tab_entry     ListMain_action_table[];
extern action_tab_entry     ListSub_action_table[];
extern action_tab_entry     EResult_action_table[];
extern action_tab_entry     FlatFile_action_table[];

static action_tab_def       user_action_table[] =
{
    /* Module name,                Action table */

    { "PUBLIC GENERAL MODULE",     msgui_public_action_table },
    { "SimpleApp",                 SimpleApp_action_table },
    { "Images_c_api",              Images_action_table },
    { "ListMain",                  ListMain_action_table },
    { "ListSub",                   ListSub_action_table },
    { "ExamResults",               EResult_action_table },
    { "FlatFileApp",               FlatFile_action_table },
    { CHARNIL }
};

action_tab_def* msgui_user_action_table = user_action_table;



7.5 Example 1: SimpleApp

File: $EMPRESSPATH/rdbms/gui/examples/ex1_actn.c
Application Name: SimpleApp
Module Name: SimpleApp
Description: Displays records from a table. Allows records to be selected, updated, deleted, inserted.
Database Interface: mx Routines
Concepts Illustrated: Initializing C API, Getting objects, Checking status code, Assigning values to fields, Getting values of fields, Setting focus to a field, Using dialogs.
 
The first part of the program contains the action table for the module. The table has an entry for each C action for the module, and the corresponding C routine to call. Note that the last entry in the action table ends is null.

/*********************************************************************
* (c) Copyright Empress Software Inc. 1983, 2006
**********************************************************************/

#include     <guicc.h>     /* for the GUI C API routines */
#include     <mscc.h>      /* for the MX routines */

#define     ACTION_TBL_NAME     SimpleApp_action_table

static void     check_field ();
static void     clear_fields ();
static void     delete ();
static void     enter_action ();
static void     exit_action ();
static void     fetch ();
static void     insert ();
static void     select_rec ();
static void     update ();

action_tab_entry     ACTION_TBL_NAME[] =

{
{ "a_check",     check_field    }, /* Each field exit action */
{ "a_clear",     clear_fields   }, /* 'Clear' pushbutton */
{ "a_delete",    delete         }, /* 'Delete' pushbutton */
{ "a_enter",     enter_action   }, /* Module enter action */
{ "a_exit",      exit_action    }, /* Module exit action */
{ "a_fetch",     fetch          }, /* 'Next', 'Previous' buttons */
{ "a_insert",    insert         }, /* 'Insert' pushbutton */
{ "a_select",    select_rec     }, /* 'Select' pushbutton */
{ "a_update",    update         }, /* 'Update' pushbutton */
{ CHARNIL }
};

The next few lines define a few constants and several global variables. Note that empgui_c_dbname is a global variable defined in Empress GUI Builder. The variables for the main window object and the field objects are made global so that any routine in the module can have access to them.

#define DB     empgui_c_dbname  /* Name of current database */
#define TAB    "employee"       /* Name of table */
#define NUM_ATTRS     4         /* Number of attributes in table */

static object mainwin;          /* MAINWIN window */
static object*     p_objs;      /* pointer to fields */

static char message_str[128]; /* used for building error messages */

/*** Null terminated lists of attribute names and field names ***/
static char* attr_names[] =
        { "name", "phone", "position", "date_hired", CHARNIL };
static char* field_names[] =
        { "name", "phone", "position", "date_hired", CHARNIL };

The first routines in the program deal with error and warning conditions. When a C API function is called, it always returns a status code to indicate whether it completed successfully or not. Depending on the function, a failure may be serious or not. For example, if empgui_c_initialize() fails, it is an error condition, and the program cannot continue. On the other hand, if empgui_c_field_set() fails, it may be considered a warning condition, and the program can continue. Typically, status messages are displayed in a warning or error dialog box. The C API functions to create dialog boxes take many arguments to allow customization; some of the routines below simplify the creation of dialogs by providing default values for most of the arguments. Note that the error_dialog() routine below also calls empgui_c_module_exit() to terminate the module after displaying the error message.

/*** Error Checking Routines ***/

#define NOSTYLE (char*)0
#define NOHELP  (char*)0

static void     print_status (str, status)   /* if not success, */
     char*          str;                     /* print status to stderr */
     gui_status     status;
{
    if (status != E_C_SUCCESS)
        fprintf (stderr, "Error: %s, Status code: %d\n", str,
        status);
}

void  info_dialog (title, message) /* display message in info dialog */
    char*   title;
    char*   message;
{
    print_status (     "Dialog info",
            empgui_c_dialog_info (NOSTYLE, title, message,
                       POSITION_CENTER ) );
}

void  error_dialog (title, message) /* display message in error
                                            dialog, */
    char*   title;                  /* and terminate module */
    char*   message;
{
    print_status (     "Dialog error",
            empgui_c_dialog_error (NOSTYLE, title, message,
                       POSITION_CENTER, NOHELP) );
    print_status ( "Exit module", empgui_c_module_exit () );
}

boolean   warning_dialog (title, message) /* display message in
                                                warning dialog, */
    char*   title;         /* return true if user clicks on OK, */
    char*   message;       /* false if user clicks on Cancel */
{
    char* selected;
    print_status (     "Dialog warning",
            empgui_c_dialog_warning (NOSTYLE, title, message,
                       POSITION_CENTER, DEFAULT_BUTTON_1, "OK",
                            "Cancel", NOHELP, &selected) );
    if (selected[0] == 'C')
        return (false);
    else
        return (true);
}

void  check_error (str, status)  /* if not success, */
    char*          str;          /* display message in error dialog */
    gui_status     status;
{
    char* message_str;

    if (status != E_C_SUCCESS)
    {
        print_status ("Status message",
            empgui_c_status_message (status, & message_str) );
        error_dialog (str, message_str);
    }
}

boolean check_warning (str, status) /* if not success, */
    char*          str;             /* display message in warning
                                                dialog */
    gui_status     status;
{
    char* message_str;
    char* selected;

    if (status != E_C_SUCCESS)
    {
        print_status ("Status message",
                empgui_c_status_message (status, & message_str) );
        return (warning_dialog (str, message_str));
    }
    return (true);
}

The first routine that will be executed when the module is started is enter_action(). Hence, this routine needs to perform initialization of the C API and get all the objects that need to be accessed in this program. empgui_c_initialize() performs initialization, and gets the main window. empgui_c_objs_get_by_names() takes an array of field names and creates an array of objects. The routine subsequently opens the table in the database, and calls the select() routine to get records from the table.

/*** Initialization: Get all objects required, and open table in
        database ***/

static  void       enter_action (obj, param)
        object     obj;
        char*      param;
{
    void select();

    mxerret = 1; /* continue on mx error */

    check_error (
        "Initialize",
        empgui_c_initialize ( & mainwin, (object*) 0, CHARNIL)
            );
    check_error (
        "Get objects IDs",
        empgui_c_objs_get_by_names ( mainwin, field_names, &
        p_objs )
            );
    if (! mxopen (DB, TAB,"d"))
        error_dialog ("Open table failed", mrerrmsg());
    select_rec (obj, "init");
}

The select() routine is executed when the user clicks on the Select button. It gets the contents of the fields and creates qualifications to select only records where the attributes match the patterns in the corresponding fields. empgui_c_props_get() is used to get the values of all the fields into an array of string pointers, which needs to be freed afterwards, using empgui_c_array_free(). The routine enter_action() also calls select(), passing it the string "init" as parameter. If that is the case, then qualifications are not needed.

/*** Get the values of the fields, and select records where
    the attributes match the patterns in the corresponding fields ***/

static  void     select_rec (obj, param)
        object   obj;
        char*    param;
{
    char**  datas;
    int     i;
    int     num=0;
    void    fetch();

    if ((param == CHARNIL) || (strcmp (param, "init") != 0))
    {
        mxgetend (); /* Terminate previous retrieval */
        check_error (
            "Get objects values",
            empgui_c_props_get ( p_objs, PROP_VALUE, &datas )
                );

        for (i=0 ; i<NUM_ATTRS; i++) /* Create qualifications */
        {
            if (**(datas+i) != (char)0) /* field is not
                                                    empty */
            {
                if (mxqmch ("match", attr_names[i],
                                *(datas+i)) )
                    num++;
                else
                {
                    sprintf (message_str,
            "Match qualification (mxqmch) failed for attribute %s",
                attr_names[i]);
            warning_dialog ("Qualification", message_str);
                }
            }
        }
        empgui_c_array_free (datas);
        for (i=num; i>1; i ) mxqand();
    }
    if ( mxgetbegin (TAB, CHARNIL ) ) /* Begin retrieval */
        fetch (obj, "Next");
    else
        warning_dialog ("Select", "Retrieval initialization
                                        failed");
}

The fetch() routine gets the next or previous record, depending on param. It is called when the user clicks on the Next or Previous pushbuttons. The parameter property of these buttons have values Next and Previous respectively, and these are passed to the fetch() routine as param.

/*** Fetch the next or previous record, and inform user in case of
        failure ***/

static  void     fetch (obj, param)
        object   obj;
        char     param;
{
    int     got_rec;
    void    display_attributes();

    switch (*param)
    {
        case 'N':   got_rec = mxget();
                      break;
        case 'P':   got_rec = mxprev();
                      break;
        default:    sprintf (message_str,
                        "Invalid Parameter %s", param);
                    error_dialog ("Fetch", message_str);
    }
    if (got_rec != 1)
    {
        clear_fields (obj, param);
        sprintf (message_str, "Cannot Fetch %s Record", param);
        warning_dialog ("Fetch", message_str);
    }
    else
        display_attributes ();
}

The display_attributes() routine reads the attribute values for the current record, and assigns these values in the corresponding fields. The attribute values are stored as an array of string pointers. empgui_c_props_set() takes the array of objects and the array of values to perform the assignment.

/*** Get values of attributes and display in corresponding fields ***/

static void display_attributes ()
{
    char* values[NUM_ATTRS];
    int i;

    for (i=0; attr_names[i] != CHARNIL ; i++)
        values[i] = mxgetvs (attr_names[i]);
    check_warning (
        "Set objects values",
            empgui_c_props_set ( p_objs, PROP_VALUE, values )
                   );
}

The clear_fields() routine simply assigns an empty string to each field, using empgui_c_field_set(). empgui_c_props_set could also be used, but it would require an array of pointers to empty strings for the values.

/*** Clear the contents of the fields ***/

static  void     clear_fields (obj, param)
        object   obj;
        char*    param;
{
    object*     pobj;
    for (pobj = p_objs ; *pobj != (object)0; pobj++)
    {
        check_warning (
            "Set field value",
            empgui_c_field_set ( * pobj, "")
                       );
    }
}

The delete() routine is executed when the Delete button is clicked. It deletes the current record, clears the fields and pops up an info dialog.

/*** Delete the current record, and inform user of success or failure ***/

static  void     delete (obj, param)
        object   obj;
        char*    param;
{
    char* str;             /* dummy pointer */
    if ( mxdel(TAB) )
    {
        clear_fields (obj, param);
        info_dialog ("Delete", "Record deleted");
    }
    else
        warning_dialog ("Delete", mrerrmsg());
}

The check_field() routine is executed when the user attempts to exit a field. It gets the contents of the current field (which is the first argument to the routine) using empgui_c_field_get(), and tries to assign the value to the corresponding attribute, which is specified by param, the second argument. If the value is not valid for the attribute, a warning dialog is popped up, and the input focus is set to the same field.

/*** Check that the content of the current field is valid for the
        corresponding attribute ***/

static  void     check_field (obj, param)
        object   obj;
        char*    param;
{
    char* data;
    check_error ( "Get field value",
                        empgui_c_field_get ( obj, & data ) );
    if (! mxputvs ( param, data ))
    {
        sprintf ( message_str, "Value '%s' not valid for
                                    attribute %s",
                    data, param );
        warning_dialog ("Field", message_str);
        check_error ( "Set field focus",
                        empgui_c_field_set_focus ( obj ) );
    }
}

The set_attrs() routine gets the values of all the fields using empgui_c_props_get(), and assigns them to the corresponding attributes. It returns an error message if any of the values are not valid.

/*** Assign the values of the fields to the corresponding attributes ***/

static char* set_attrs ()
{
    char**  datas;
    int     i;

    check_warning (
        "Get objects values",
        empgui_c_props_get ( p_objs, PROP_VALUE, &datas )
                   ); 

    for (i=0 ; i<NUM_ATTRS; i++)
    {
        if (! mxputvs (attr_names[i], *(datas+i)) )
        {
            sprintf ( message_str,
                        "Value '%s' not valid for attribute %s",
                            *(datas+i), attr_names[i] );
            empgui_c_array_free (datas);
            return (message_str);
        }
    }
    empgui_c_array_free (datas);
    return (CHARNIL);
}

The insert() routine is called when the Insert button is clicked. It assigns the values of the fields to the attributes, and inserts a new record.

/*** Insert a new record using the values in the fields,
        and inform user of success or failure ***/

static  void     insert (obj, param)
        object   obj;
        char*    param;
{
    char* message;
    if ((message = set_attrs()) != CHARNIL)
        warning_dialog ("Insert", message);
    else
        if ( mxadd(TAB) )
            info_dialog ("Insert", "Record inserted");
        else
            warning_dialog ("Insert", mrerrmsg());
}

The update() routine is called when the Update button is clicked. It assigns the values of the fields to the attributes, and updates the current record.

/*** Update the current record using the values in the fields,
        and inform user of success or failure ***/

static  void     update (obj, param)
        object   obj;
        char*    param;
{
    char* message;
    if ((message = set_attrs()) != CHARNIL)
        warning_dialog ("Update", message);
    else
        if ( mxput(TAB) )
            info_dialog ("Update", "Record updated");
        else
            warning_dialog ("Update", mrerrmsg());
}

The exit_action() routine is called when the module is terminated. It is used for clean-up purposes.

/*** Cleanup: Terminate retrieval, and close table. ***/

static  void     exit_action (obj, param)
        object   obj;
        char*    param;
{
    mxgetend ( );
    mxaclose ( );
    print_status ( "Array free", empgui_c_array_free ( p_objs ) );
}



7.6 Example 2: Images_c_api

File: $EMPRESSPATH/rdbms/gui/examples/ex2_actn.c
Application Name: Images_c_api
Module Name: Images_c_api
Description: Displays records from a table containing pictures. Allows records to be automatically retrieved and displayed with a user-controllable time interval.
Database Interface: mr Routines
Concepts Illustrated: Handling image bulk data, Getting & setting scale value, Accessing the menubar, Controlling sensitivity of objects, Hiding & showing a window.
 

The first part of the program contains the action table for the module. The table has an entry for each C action for the module, and the corresponding C routine to call. Note that the last entry in the action table ends is null.

/***********************************************************************
* (c) Copyright Empress Software Inc. 1983, 2006
***********************************************************************/

#include <guicc.h>     /* for the GUI C API routines */
#include <mscc.h>      /* for the MR routines */

#define     ACTION_TBL_NAME     Images_action_table

static void     next ();
static void     prev ();
static void     slide ();
static void     show_win ();
static void     enter_action ();
static void     exit_action ();

action_tab_entry ACTION_TBL_NAME[] =
{
{ "next_pic",     next           }, /* 'Next' pushbutton */
{ "prev_pic",     prev           }, /* 'Previous' pushbutton */
{ "slide_mode",   slide          }, /* 'Slide Mode' toggle */
{ "show_scale",   show_win       }, /* 'Show Scale' toggle */
{ "select_pic",   enter_action   }, /* Module enter action */
{ "cleanup",      exit_action    }, /* Module exit action */
{ CHARNIL }
};

The following section defines several global variables for the various objects that need to be accessed in this program, as well as several mr descriptors.

#define TAB "pictures"

static object name_fld;      /* name field */
static object categ_fld;     /* category field */
static object image_fld;     /* image field */

static object next_pbn;      /* Next pushbutton */
static object prev_pbn;      /* Prev pushbutton */
static object slide_tbn;     /* slide mode togglebutton */

static object view_cbn;      /* View cascade button */
static object show_tbn;      /* show scale togglebutton */
static object interval_scl;  /* time interval scale */
static object time_win;      /* time interval window */

static addr timerid;         /* id for timer mechanism */

static addr table;           /* table descriptor */
static addr record;          /* record descriptor */
static addr retrieval;       /* retrieval descriptor */

static addr a_name;          /* attribute descriptors */
static addr a_category;
static addr a_image;

For dialog and error checking routines, we can use those defined in the SimpleApp module. So, these routines are declared as extern here. In addition, the mr_error() routine handles errors generated by the mr routines.

/*** Dialog and Error checking routines ***/

extern void     info_dialog ();
extern void     error_dialog ();
extern boolean  warning_dialog ();
extern void     check_error ();
extern boolean  check_warning ();

static  void     mr_error (str)   /* When an MR routine fails, */
        char*    str;             /* display message in error dialog */
{
    error_dialog (str, mrerrmsg());
}

The enter_action() routine is the first one to be executed when running the module. It performs initialization of the C API using empgui_c_initialize() and gets the main window, the menubar and other bins (such as the time_interval window and the view pull-down menu). Fields and buttons in the main window are obtained using empgui_c_objs_get_by_name_pair(). The scale in the time_interval window, the toggle button in the view menu and the view cascade button in the menubar are all obtained using calls to empgui_c_obj_get_by_name().

/*** Action executed when starting this module ***/

static  void     enter_action (obj, param)
        object   obj;
        char*    param;
{
    object  mainwin;
    object  menubar;
    object  view_menu;
    void    next();

    /* c_api initialization */
    check_error (
        "Initialize",
        empgui_c_initialize (   & mainwin, & menubar,
        "time_interval",        & time_win,
        "view",                 & view_menu, CHARNIL)
                );
    check_error (
        "Get objects in mainwin",
        empgui_c_objs_get_by_name_pair ( mainwin,
                "name",           & name_fld,
                "category",       & categ_fld,
                "image",          & image_fld,
                "Next",           & next_pbn,
                "Previous",       & prev_pbn,
                "slide_mode",     & slide_tbn,
                CHARNIL)
                );
    check_error (
        "Get scale object in time window",
        empgui_c_obj_get_by_name ( time_win, "interval", & interval_scl)
                );
    check_error (
        "Get toggle button in view menu",
        empgui_c_obj_get_by_name ( view_menu, "show_scale", & show_tbn )
                );
    check_error (
        "Get view button in menubar",
        empgui_c_obj_get_by_name ( menubar, "view", & view_cbn )
                );

After obtaining all the objects, the next step is to make the view cascade button insensitive by calling empgui_c_sen_set(), and to set the initial scale value to 5 using empgui_c_scale_set(). Then the database initialization is performed - opening the pictures table, creating table, record, attribute descriptors, etc.

    check_warning (
        "Set View button insensitive",
        empgui_c_sen_set ( view_cbn, false )
                  );
    check_warning (
        "Set scale value",
        empgui_c_scale_set ( interval_scl, 5 )
                  );

    /* database routines */
    if ((table = mrtopen (empgui_c_dbname, TAB, 'r')) == ADDRNIL)
    {
        mr_error ("Open Table");
    }
    record = mrmkrec (table);
    a_name = mrngeta (table, "name");
    a_category = mrngeta (table, "category");
    a_image = mrngeta (table, "image");

    if ((retrieval = mrtgtbegin (ADDRNIL, record, ADDRNIL)) == ADDRNIL)
    {
        mr_error ("Select records");
    }
    next (obj, param);
}

The next() and prev() routines are called when activating the User-Define and Prev buttons respectively. They fetch the next or previous record, and call the display() routine. The latter checks the status of the fetch - if there are no more records, an information dialog is displayed. If the record was obtained, the values of the name and category attributes are retrieved and displayed in the respective fields using empgui_c_field_set(), and the data in the image attribute is displayed in the image field using empgui_c_image_set().

/*** Get attributes values from fetched record and copy to fields ***/

static  void     display (state, param)
        int      state;
        char*    param;
{
    char   title[32];
    void   clear_fields ();

    sprintf (title, "%s Picture", param);
    switch (state)
    {
        case 0: clear_fields ();
                info_dialog (title, "No more pictures.");
                break;

        case 1: check_warning ( "set field value",
                                empgui_c_field_set ( name_fld,
                                mrgetvs (record, a_name) ) );
                check_warning ( "set field value",
                                empgui_c_field_set ( categ_fld,
                                mrgetvs (record, a_category) ) );
                check_warning ( "set image field value",
                                empgui_c_image_set ( image_fld,
                                mrgeti (record, a_image) ) );
                break;
        default: mr_error ("Fetch");
    }
}

/*** Action for "Next" pushbutton: fetch the next record and display it
            ***/

static  void     next (obj, param)
        object   obj;
        char*    param;
{
    display ( mrtget (retrieval), "Next" );
}

/*** Action for "Previous" pushbutton: fetch previous record and display
        it ***/

static  void     prev (obj, param)
        object   obj;
        char*    param;
{
    display ( mrtprev (retrieval), "Previous" );
}

To clear the contents of the text fields, an empty string is passed to empgui_c_field_set(). To clear the contents of an image field, an empty image must be passed to empgui_c_image_set(). Since the first 4 or 8 bytes (depending on sizeof(long) ) of the image bulk structure indicate the size (number of bytes) of the data, an empty image is simply (long)0.

/*** Set text fields and image field to null ***/

static void clear_fields ()
{
    long null_image = 0;

    check_warning ( "set field value",
                    empgui_c_field_set ( name_fld, "" ) );
    check_warning ( "set field value",
                    empgui_c_field_set ( categ_fld, "" ) );
    check_warning ( "set image field value",
                    empgui_c_image_set ( image_fld, & null_image ) );
}

The slide() routine is called when the Slide Mode toggle button is activated. First, it gets the state of the toggle, using empgui_c_toggleb_get(). If the toggle is on, then it calls next_loop() to fetch records continuously. If the toggle is off, then it stops the automatic fetching by removing the timer using empgui_c_timer_trash(), and makes sure that the scale window is hidden by setting the Show Scale toggle off using empgui_c_toggleb_set().

/*** Action for "slide_mode" togglebutton:
     if togglebutton is on, start automatic fetching of records
     if togglebutton is off, stop automatic fetching ***/

static  void     slide (obj, param)
        object   obj;
        char*    param;
{
    boolean   slide_on;
    void      next_loop();
    void      pushb_sensitivity();

    check_error (
            "Get toggle value",
            empgui_c_toggleb_get ( slide_tbn, & slide_on )
                );
    if (slide_on)
    {
        pushb_sensitivity ( false );
        next_loop ();
    }
    else
    {
        check_error (
            "Remove timer",
            empgui_c_timer_trash (timerid)
                    );
        check_warning (
            "Set toggle off",
            empgui_c_toggleb_set ( show_tbn, false )
                  );
        pushb_sensitivity ( true );
    }
}

The next_loop() routine fetches a record and displays it. Then it gets the value of the scale using empgui_c_scale_get(), and sets up a timer to call itself using empgui_c_timer_add().

/*** Fetch next record. If no next record, restart from first record.
        Set timer to call same function in 5 seconds ***/

static void next_loop ()
{
    int   state;
    int   secs;

    state = mrtget(retrieval);
    if (state==0)
    {
        mrgetend (retrieval);
        if ((retrieval = mrtgtbegin (ADDRNIL, record, ADDRNIL))
                                                    == ADDRNIL)
        {
            mr_error ("Select records");
        }
        state = mrtget(retrieval);
    }
    display (state, "Next");
    check_error (
            "Get scale value",
            empgui_c_scale_get ( interval_scl, & secs )
                );
                    /* set timer to fetch again in secs
                                seconds */
    check_error (
            "Set timer",
            empgui_c_timer_add (secs * 1000, next_loop, (addr)0,
                                        &timerid)
                );
}

When going into slide mode, the Next and Previous buttons must be made insensitive. Also, the view cascade button must be made sensitive. The reverse is true when getting out of slide mode. The pushb_sensitivity() routine is called by the slide() routine, and it uses empgui_c_sens_set_all() to control the sensitivity of the the two pushbuttons, and empgui_c_sen_set() to control the sensitivity of the cascade button.

/*** Set the sensitivity of "Next" and "Prev" pushbuttons ***/

static  void        pushb_sensitivity (on_or_off)
        boolean     on_or_off;
{
    object pbn_lst[3];
    pbn_lst[0] = next_pbn;
    pbn_lst[1] = prev_pbn;
    pbn_lst[2] = (object)0;

    check_warning (
            "Set pushbutton sensitivities",
            empgui_c_sens_set_all ( pbn_lst, on_or_off )
                  );
    check_warning (
            "Set View button sensitivity",
            empgui_c_sen_set ( view_cbn, ! on_or_off )
                  );
}

The show_win() routine is called when activating the Show Scale toggle button in the view pulldown menu. It gets the state of the toggle using empgui_c_toggleb_get(). If the toggle is on, it displays the time_win window using empgui_c_window_show(). If the toggle is off, it hides the window using empgui_c_window_hide().

/*** Show or hide the scale window depending on the Show Scale toggle
        ***/

static  void     show_win (obj, param)
        object   obj;
        char*    param;
{
    boolean show_on;

    check_error (
            "Get toggle value",
            empgui_c_toggleb_get ( show_tbn, & show_on )
                );
    if (show_on)
        check_warning (
                "Show scale window",
                empgui_c_window_show ( time_win )
                      );
    else
        check_warning (
                "Hide scale window",
                empgui_c_window_hide ( time_win )
                      );
}

The exit_action() routine is only executed when exiting the module. In this case, it simply de-allocates the descriptors used, and closes the pictures table.

/*** Action executed when exiting this module: clean-up descriptors ***/

static  void     exit_action (obj, param)
        object   obj;
        char*    param;
{
    mrgetend (retrieval);
    mrfrrec (record);
    mrclose (table);
}



7.7 Example 3: ListApp

File: $EMPRESSPATH/rdbms/gui/examples/ex3_actn.c
Application Name: ListApp
Module Names: ListMain, ListSub
Description: Displays records in a multilist. Double-clicking on the multilist causes a sub-module to appear, displaying the record clicked on. The sub-module allows deletion, update, insertion, as well as selection of records to be displayed in the multilist.
Database Interface: Embedded SQL
Concepts Illustrated: Use of multilist, Sensitivity of buttons, Using multiple modules.
 

Since the application has two modules, the program will have two action tables. Both are defined at the beginning of the program.

/***********************************************************************
*     (c) Copyright Empress Software Inc. 1983, 2006
***********************************************************************/

#include <guicc.h>     /* for the GUI C API routines */
#include <mscc.h>      /* for embedded SQL precompiler */

#define     ACTION_TBL_NAME1     ListMain_action_table
#define     ACTION_TBL_NAME2     ListSub_action_table

static void     select_record ();
static void     enter_main ();
static void     exit_main ();

action_tab_entry     ACTION_TBL_NAME1[] =
{
{ "enter_list",       enter_main     }, /* Main module enter action */
{ "exit_list",        exit_main      }, /* Main module exit action */
{ "select_record",    select_record  }, /* 'list' double click action */
{ CHARNIL }
};

static void     enter_sub ();
static void     exit_sub ();
static void     clear ();
static void     delete_rec ();
static void     insert_rec ();
static void     update_rec ();
static void     select_list ();

action_tab_entry     ACTION_TBL_NAME2[] =
{
{ "enter_record",     enter_sub     },  /* Sub module enter action */
{ "exit_record",      exit_sub      },  /* Sub module exit action */
{ "clear_fields",     clear         },  /* 'Clear' pushbutton */
{ "delete",           delete_rec    },  /* 'Delete' pushbutton */
{ "insert",           insert_rec    },  /* 'Insert' pushbutton */
{ "update",           update_rec    },  /* 'Update' pushbutton */
{ "select",           select_list   },  /* 'Select' pushbutton */
{ CHARNIL }
};

Global variables are defined for objects that the program needs to access. Note that the Delete and Update pushbuttons are needed because the program needs to control their sensitivity.

static object list_obj;     /* In main module: multilist */

static object name_fld;     /* In sub module: name field */
static object phone_fld;    /* phone field */
static object position_fld; /* position field */
static object date_fld;     /* date field */
static object delete_btn;   /* delete pushbutton */
static object update_btn;   /* update pushbutton */

The next section defines two constants for the select statements: the first query is for the main module, the second is for the sub module. The variables used in embedded SQL statements are in the SQL variable declaration section.

#define   SELECT_1   "select name, position from employee"
#define   SELECT_2   "select name, phone, position, date_hired
                     from employee where name = '%s'"
EXEC SQL INCLUDE SQLCA;
EXEC SQL BEGIN DECLARE SECTION;
static char   dbname[128];              /* Name of database */
static char   query[1024];              /* Query to be prepared */
static char   name_array[100][33];      /* Array of names and ... */
static char   position_array[100][33];  /* ...positions for multilist */
static char   name_str[33];             /* Value of name attribute */
static char   phone_str[15];            /* ...phone attribute */
static char   position_str[128];        /* ...position attribute */
static char   date_str[20];             /* ...date attribute */
static char*  name_ptr;                 /* Value of name field */
static char*  phone_ptr;                /* ...phone field */
static char*  position_ptr;             /* ...phone field */
static char*  date_ptr;                 /* ...phone field */
static short  c1, c2, c3, c4;           /* Control variables */
EXEC SQL END DECLARE SECTION;

For dialog and error checking routines, we can use those defined in the SimpleApp module. So, these routines are declared as extern here. In addition, the check_sql_error() routine checks for error conditions after SQL operations.

/*** Dialog and Error checking routines ***/

extern void         info_dialog ();
extern void         error_dialog ();
extern boolean      warning_dialog ();
extern void         check_error ();
extern boolean      check_warning ();

static  void     check_sql_error (str)
        char*    str;
{
    if (SQLCODE != 0)
        error_dialog ( str, SQLERRMC );
}

The enter_main() routine is executed when starting the main module. It performs SQL initialization, specifies the default database, prepares the query and declares a cursor for the query. Then it performs C API initialization and gets the main window using empgui_c_initialize() and the multilist object using empgui_c_obj_get_by_name().

/*** Routines for ListMain module ***/
/*** Enter action for ListMain module:
     Initialize embedded SQL, prepare query and declare cursor.
     Initialize C API, get main window and the list object. ***/

static  void     enter_main (obj, param)
        object   obj;
        char*    param;
{
    object mainwin;
    void set_list ();

    strcpy (dbname, empgui_c_dbname);
    EXEC SQL INIT;
    check_sql_error ("SQL initialize");
    EXEC SQL DATABASE IS :dbname;
    check_sql_error ("Database initialize");

    strcpy (query, SELECT_1);
    EXEC SQL PREPARE s1 FROM :query;
    check_sql_error ("Prepare query");

    EXEC SQL DECLARE c1 CURSOR FOR s1;
    check_sql_error ("Declare cursor");
    check_error (
            "Initialize",
            empgui_c_initialize ( & mainwin, (object*) 0, CHARNIL)
                );
    check_error (
            "Get list object",
            empgui_c_obj_get_by_name ( mainwin, "list", & list_obj )
                );

    set_list ();
}

The set_list() routine selects and fetches all the records specified in the first cursor. The values of name and position attributes are stored in arrays of character arrays. However, empgui_c_list_set_items(), the function to assign items to a list or multilist, takes arrays of pointers to strings. So, two arrays of character pointers are set up to point to each element of the data arrays, and these arrays of pointers are passed to empgui_c_list_set_items().

/*** Open the cursor, fetch all the records, put the values of name
      and position attributes in the multilist, and close the cursor ***/

static void   set_list ()
{
    int     i;
    int     num_recs;
    char*   list1[100];
    char*   list2[100];

    EXEC SQL OPEN c1;
    check_sql_error ("Open cursor");

    for (i=0; (i<100) && (SQLCODE==0); i++)
    {
        EXEC SQL FETCH c1 INTO  :name_array[i] :c1,
                                :position_array[i] :c3;
        if (SQLCODE != 100)
            check_sql_error ("Fetch");
        if (c1 < 0)
            name_array[i][0] = (char)0;
        if (c3 < 0)
            position_array[i][0] = (char)0;
        list1[i] = name_array[i];
        list2[i] = position_array[i];
    }
    if (i == 100)
        warning_dialog ("Employee List", "Too many records");
    num_recs = i 1;     /* as there was no record when i was last
                                incremented */
    check_warning (
            "Set list contents",
            empgui_c_list_set_items ( list_obj, num_recs,
            list1, list2, (char**)0 )
                  );

    EXEC SQL CLOSE c1;
    check_sql_error ("Open cursor");
}

When the user double-clicks on a row of the multilist, that row is selected, and the sub module is called to display the corresponding record. The select_record() routine gets the selected row number of the multilist using empgui_c_prop_get(), gets the corresponding name from the array of names, and calls the sub module using empgui_c_module_run(), passing it the name as argument.

/*** Get the name in the selected row of the multilist,
        and call the ListSub module ***/

static  void     select_record (obj, param)
        object   obj;
        char*    param;
{
    int     row;
    char*   argv[1];

    check_error (
        "Get selected row in list",
        empgui_c_prop_get ( obj, PROP_VALUE, & row )
                );
    if (row < 1)
    {
        warning_dialog ("List", "Invalid row selected");
        return;
    }
    argv[0] = name_array[row-1];
    check_error (
        "call module",
        empgui_c_module_run ( "ListSub", 1, argv)
                );
}

The exit action of the main module has to clean up data structures used by embedded SQL. However, if EXEC SQL EXIT is performed, then all Empress data structures will be cleaned up, including those used by Empress GUI Builder. Therefore, the special statement EXEC SQL EXIT_SQL is used here - it only cleans up embedded SQL data structures.

/*** Cleanup embedded SQL data structures before exiting the main module
        ***/

static  void     exit_main (obj, param)
        object   obj;
        char*    param;
{
    EXEC SQL EXIT_SQL;
}

The enter action for the sub module has four arguments, instead of the usual two, because the name from the selected row is passed in argv[0]. It uses the name to build a query to fetch that record only. Note that the sub module's enter action also needs to call empgui_c_initialize() to get its main window. Then it gets the fields and pushbuttons needed using empgui_c_objs_get_by_name_pair(). The values of the attributes of the fetched record are displayed in the corresponding fields using empgui_c_field_set().

/*** Routines for ListSub module ***/
/*** Enter action for ListSub module.
     Initialize C API, get main window, fields and pushbuttons. 
     Prepare query and fetch record selected from multilist. ***/

static  void     enter_sub (obj, param, argc, argv)
        object   obj;
        char*    param;
        int      argc;
        char*    argv[];
{
    static object mainwin2;
    sprintf (query, SELECT_2, argv[0]);
    EXEC SQL PREPARE s2 FROM :query;
    check_sql_error ("Prepare query");

    /* module may be called many times, but only need initialization once */
    if (mainwin2 == (object)0)
    {
        check_error (
            "Initialize submodule",
            empgui_c_initialize ( & mainwin2, (object*) 0,
                                    CHARNIL )
                    );
        check_error (
            "Get fields objects",
            empgui_c_objs_get_by_name_pair ( mainwin2,
                                "name", & name_fld,
                                "phone", & phone_fld,
                                "position", & position_fld,
                                "date_hired", & date_fld,
                                "deleteb", & delete_btn,
                                "updateb", & update_btn,
                                CHARNIL )
                    );
        EXEC SQL DECLARE c2 CURSOR FOR s2 FOR UPDATE;
        check_sql_error ("Declare cursor");
    }
    EXEC SQL OPEN c2;
    check_sql_error ("Open cursor");

    EXEC SQL FETCH c2 INTO :name_str :c1, :phone_str :c2,
                           :position_str :c3, :date_str :c4;
    clear (obj, param);
    if (SQLCODE != 100)
        check_sql_error ("Fetch");
    if (c1 < 0)
        check_warning ( "Set field value",
            empgui_c_field_set ( name_fld, name_str ) );
    if (c2 < 0)
        check_warning ( "Set field value",
            empgui_c_field_set ( phone_fld, phone_str ) );
    if (c3 < 0)
        check_warning ( "Set field value",
            empgui_c_field_set ( position_fld, position_str ) );
    if (c4 < 0)
        check_warning ( "Set field value",
            empgui_c_field_set ( date_fld, date_str ) );
}

To get the the contents of each field, the function empgui_c_field_get() is used.

/*** Get the contents of the fields ***/

static void read_fields ()
{
    check_error ( "Get field value",
        empgui_c_field_get ( name_fld, & name_ptr ) );
    check_error ( "Get field value",
        empgui_c_field_get ( phone_fld, & phone_ptr ) );
    check_error ( "Get field value",
        empgui_c_field_get ( position_fld, & position_ptr ) );
    check_error ( "Get field value",
        empgui_c_field_get ( date_fld, & date_ptr ) );
}

The select_list() routine is called when the Select button is clicked. It builds conditions for the WHERE clause of the first query (for the multilist), matching attributes to fields. Note that the new query is prepared again, so that it is associated with prepared statement s1, but the cursor c1 does not have to be declared again - it is already associated with statement s1. Finally, the sub module is exited using empgui_c_module_exit().

/*** Modify the query for the main module by building a WHERE clause
     matching the contents of fields to attributes, and exit the sub
     module ***/

static  void     select_list (obj, param)
        object   obj;
        char*    param;
{
    int num_conds = 0;
    read_fields ();
    strcpy (query, SELECT_1);
    if (strlen (name_ptr) != 0)
    {
        num_conds ++ ;
        strcat (query, " where name match '");
        strcat (query, name_ptr);
        strcat (query, "'");
    }
    if (strlen (phone_ptr) != 0)
    {
        num_conds ++ ;
        if (num_conds==1)
            strcat (query, " where");
        else
            strcat (query, " and");
        strcat (query, " phone match '");
        strcat (query, phone_ptr);
        strcat (query, "'");
    }
    if (strlen (position_ptr) != 0)
    {
        num_conds ++ ;
        if (num_conds==1)
            strcat (query, " where");
        else
            strcat (query, " and");
        strcat (query, " position match '");
        strcat (query, position_ptr);
        strcat (query, "'");
    }
    if (strlen (date_ptr) != 0)
    {
        num_conds ++ ;
        if (num_conds==1)
            strcat (query, " where");
        else
            strcat (query, " and");
        strcat (query, " date_hired = '");
        strcat (query, date_ptr);
        strcat (query, "'");
    }

    EXEC SQL PREPARE s1 FROM :query;
    check_sql_error ("Prepare query");

    empgui_c_module_exit ();
}

The clear() routine is called when the Clear button is clicked. It empties the fields by assigning empty strings using empgui_c_field_set().

/*** Clear the contents of the fields ***/

static  void     clear (obj, param)
        object   obj;
        char*    param;
{
    check_warning ( "Set field value",
        empgui_c_field_set ( name_fld, "" ) );
    check_warning ( "Set field value",
        empgui_c_field_set ( phone_fld, "" ) );
    check_warning ( "Set field value",
        empgui_c_field_set ( position_fld, "" ) );
    check_warning ( "Set field value",
        empgui_c_field_set ( date_fld, "" ) );
}

After deleting a record, there is no longer a current record. And after inserting a new record, the new record is not the current record. Hence, the delete and update operations should be disabled by making their pushbuttons insensitive, using empgui_c_sen_set().

/*** Make the 'Delete' and 'Update' buttons insensitive ***/

static void make_insensitive ()
{
    check_warning (
        "Set delete button insensitive",
        empgui_c_sen_set ( delete_btn, false )
                  );
    check_warning (
        "Set update button insensitive",
        empgui_c_sen_set ( update_btn, false )
                  );
}

/*** Delete the current record ***/
static  void     delete_rec (obj, param)
        object   obj;
        char*    param;
{
    EXEC SQL DELETE FROM employee WHERE CURRENT OF c2;
    check_sql_error ("Delete");
    clear (obj, param);
    make_insensitive ();
}

/*** Insert a new record ***/
static  void     insert_rec (obj, param)
        object   obj;
        char*    param;
{
    read_fields ();
    EXEC SQL INSERT INTO employee (name, phone, position, date_hired)
        VALUES (:name_ptr, :phone_ptr, :position_ptr, :date_ptr);
    check_sql_error ("Insert");
    make_insensitive ();
}

/*** Update the current record ***/
static  void     update_rec (obj, param)
        object   obj;
        char*    param;
{
    read_fields ();
    EXEC SQL UPDATE employee SET name = :name_ptr, phone = phone_ptr,
        position = :position_ptr, date_hired = :date_ptr
        WHERE CURRENT OF c2;
    check_sql_error ("Update");
}

The exit action of the sub module closes the cursor used to fetch the record displayed and makes the Update and Delete buttons sensitive again (in case they were de-sensitized after an insert or delete operation). Then it calls set_list() to update the multilist. This will cause any delete, update or insert to be reflected in the multilist, and also if the Select button was used to select only records matching the fields.

/*** Before quitting the sub module, close the cursor that it used,
      re-select the cursor for the main module, and re-fill the list ***/

static  void     exit_sub (obj, param)
        object   obj;
        char*    param;
{
    EXEC SQL CLOSE c2;
    check_sql_error ("Close cursor");

    check_warning (
            "Set delete button sensitive",
            empgui_c_sen_set ( delete_btn, true )
                  );
    check_warning (
            "Set update button sensitive",
            empgui_c_sen_set ( update_btn, true )
                  );
    set_list ();
}



7.8 Example 4: ExamResults

File: $EMPRESSPATH/rdbms/gui/examples/ex4_actn.c
Application Name: ExamResults
Module Name: ExamResults
Description: Displays a bar chart showing the distribution of grades obtained by students in a particular subject. A pushbutton allows the results of the next subject to be displayed.
Database Interface:  Embedded SQL
Concepts Illustrated: Using the user object as a drawing area.
 

The program needs to include several X11 and Xm header files because of the X and Motif functions used.

/***********************************************************************
*     (c) Copyright Empress Software Inc. 1983, 2006
***********************************************************************/

#include <guicc.h>     /* for the GUI C API routines */
#include <mscc.h>      /* for the embedded SQL precompiler */

#include <X11/Xlib.h>  /* for the X and Motif calls */
#include <X11/Intrinsic.h>
#include <Xm/Xm.h>

#define     ACTION_TBL_NAME     EResult_action_table

static void     next ();
static void     enter_action ();
static void     exit_action ();
static void     user_init ();

action_tab_entry     ACTION_TBL_NAME[] =
{
{ "next_subject",    next           },  /* 'Next Subject' pushbutton */
{ "enter",           enter_action   },  /* Module enter action */
{ "exit",            exit_action    },  /* Module exit action */
{ "user_obj_init",   user_init      },  /* User object init action */
{ CHARNIL }
};

Variables used in embedded SQL statements are in the SQL variable declaration section. Global variables are defined for GUI and X objects, and also for data that needs to be accessed by the routines which fetch data and display the data.

/*** Embedded SQL declarations and other global variables ***/

#define Select_Results     "select grade, count from exam_results
                            where subject = '%s' group by grade"
EXEC SQL INCLUDE SQLCA;
EXEC SQL BEGIN DECLARE SECTION;
static char  dbname[128];       /* Name of current database */
static char  subjects[20][16];  /* Names of subjects studied */
static char  grades[10][8];     /* All the possible grades */
static char  query[128];        /* SQL select statement */
static char  grade[8];          /* Value of grade attribute */
static int   num;               /* No of records with the same
                                    grade */
EXEC SQL END DECLARE SECTION;
static Display     *this_display;
static Widget      DrawingArea_widget;
static GC          DrawingArea_GC;

static object  subject_fld;
static int     num_subjects;     /* Number of subjects available */
static int     num_grades;       /* Number of possible grades */
static int     current_subject;
static int     results[10];      /* No of students for each grade */
static char    results_str[10][8]; /* ... in string format */

The error checking routines defined in modules SimpleApp and ListApp will be used.

/*** Error Checking Routines ***/

extern void     error_dialog ();
extern void     check_error ();
extern void     check_sql_error ();

The enter_main() routine is the module enter action. It performs all the necessary database initialization.

/*** Initialization: Find what subjects and what grades are available
        ***/

static  void     enter_action (obj, param)
        object   obj;
        char*    param;
{
    object  mainwin;
    int     i;
    void    get_results();

    EXEC SQL INIT;
    check_sql_error ("SQL initialize");
    strcpy (dbname, empgui_c_dbname);
    EXEC SQL DATABASE IS :dbname;
    check_sql_error ("Database initialize");

A query is formulated to find all the subjects available. The names of the subjects are stored in global array subjects[].

    EXEC SQL DECLARE c_subjects CURSOR FOR
        "select distinct subject from exam_results sort by
        subject";
    check_sql_error ("Declare cursor");
    EXEC SQL OPEN c_subjects;
    check_sql_error ("Open cursor");

    for (i=0; (i<20) && (SQLCODE==0); i++)
    {
        EXEC SQL FETCH c_subjects INTO :subjects[i];
        if (SQLCODE != 100)
            check_sql_error ("Fetch");
    }
    num_subjects = i 1;
    EXEC SQL CLOSE c_subjects;

Another query is formulated to find all the possible grades. These are stored in global array grades[].

    EXEC SQL DECLARE c_grades CURSOR FOR
        "select distinct grade from exam_results sort by grade";
        check_sql_error ("Declare cursor");
    EXEC SQL OPEN c_grades;
    check_sql_error ("Open cursor");

    for (i=0; (i<20) && (SQLCODE==0); i++)
    {
        EXEC SQL FETCH c_grades INTO :grades[i];
        if (SQLCODE != 100)
            check_sql_error ("Fetch");
    }
    num_grades = i-1;
    EXEC SQL CLOSE c_grades;

After initializing the C API and getting the subject field, a query is prepared to get the count of records with the same grades for the first subject.

    /* Perform C API initialization */

    check_error (
        "Initialize",
        empgui_c_initialize ( & mainwin, (object*) 0, CHARNIL)
                );
    check_error (
        "Get field objects",
        empgui_c_obj_get_by_name (mainwin, "subject",
        &subject_fld)
                );

    /* Formulate the query to get the results for first subject */

    current_subject = 0;
    sprintf (query, Select_Results, subjects[current_subject]);
    EXEC SQL PREPARE s_results FROM :query;
    EXEC SQL DECLARE c_results CURSOR FOR s_results;
    get_results();
}

The get_results() routine gets the grades (and the their counts) for the current subject, and stores their counts into integer global array results[], and string global array results_str[]. The routine has to handle cases where there is no record for a particular grade for the current subject, in which case the count should be set to zero. The current subject name is displayed in the corresponding field.

    /*** Get distribution of results for the current subject ***/

    static void get_results()
    {
        int i, j;
        EXEC SQL OPEN c_results;
        check_sql_error ("Open cursor");

        for (i=0, j=0; (j<num_grades) && (SQLCODE==0); i++, j++)
        {
            EXEC SQL FETCH c_results INTO :grade, :num;
            if (SQLCODE != 100)
            {
                check_sql_error ("Fetch");
                while (strcmp (grade, grades[j]) != 0)
                {
                    strcpy (results_str[j], "0");
                    results[j++] = 0;
                    if (j == num_grades)
                    {
                        fprintf (stderr,"Grade %s unknown!\n",
                                grade);
                        msexit ();
                    }
                }
                sprintf (results_str[j], "%d", num);
                results[j] = num;
            }
        }
        if (SQLCODE == 100)
            for (i=j 1; i<num_grades; i++)
            {
                results[i] = 0;
                strcpy (results_str[i], "0");
            }
        EXEC SQL CLOSE c_results;
        check_error (
            "Set field value",
            empgui_c_field_set ( subject_fld,
            subjects[current_subject] )
                    );
    }

The user_init() routine gets the widget for the user object. Then it uses several Xlib and Xt routines to get the display, to set the callbacks for the resize and expose events, and to create a graphic context.

    /*** Perform C API Ininitialization, get field and user object
        and get the display, drawing area widget, window, and gc ***/

    static  void     user_init (obj, param)
            object   obj;
            char*    param;
    {
        XGCValues   GC_vals;
        void        draw_chart();

        check_error (
            "Get user widget",
            empgui_c_user_get_widget (obj, &DrawingArea_widget)
                    );

        this_display = XtDisplay (DrawingArea_widget);
        XtAddCallback (DrawingArea_widget, XmNresizeCallback,
                        draw_chart, 0);
        XtAddCallback (DrawingArea_widget, XmNexposeCallback,
                        draw_chart, 0);
        XtVaGetValues (DrawingArea_widget, XtNforeground,
                        &(GC_vals.foreground), XtNbackground,
                        &(GC_vals.background), NULL);
        GC_vals.line_width = 4;
        GC_vals.font = XLoadFont (this_display,
            " * courier bold * * 14 100 100 100 * * * *");
        DrawingArea_GC = XtGetGC (DrawingArea_widget,
                GCForeground | GCBackground | GCLineWidth | GCFont,
                &GC_vals);
    }

The draw_object() routine gets the height and width of the user widget. Then it finds the highest count for the grade results. Using the user widget as a drawing area, it writes the grade, draws a rectangle of a width proportional to the corresponding count, and writes the count for each grade. Note that the height of the rectangles and the vertical distance between them depend on the number of possible grades and the height of the user widget.

    /*** Draw the chart to illustrate the distribution of results,
        use the user object as a canvas for drawing text and rectangles ***/

    static void draw_chart (w, client_data, call_data)
    {
        int        i;
        int        highest;
        int        ypos;
        int        height_rect;
        Dimension  DrawingArea_width;
        Dimension  DrawingArea_height;
        Window     DrawingArea_window;

        DrawingArea_window = XtWindow (DrawingArea_widget);
        XClearWindow (this_display, DrawingArea_window);
        XtVaGetValues (DrawingArea_widget, XmNwidth,
                    &DrawingArea_width, XmNheight,
                    &DrawingArea_height, NULL);
        highest = results[0];
        for (i=0; i < num_grades; i++) /* Find the highest number */
        {
            if (results[i] > highest)
            highest = results[i];
        }
        if (highest == 0) /* To avoid division by zero */
            highest = 1;
        if ( DrawingArea_height < 100 ) /* Set min size for area */
            DrawingArea_height = 100;
        if ( DrawingArea_width < 100 )
            DrawingArea_width = 100;
        height_rect = DrawingArea_height/(num_grades*2+1);
        for (i=0; i < num_grades; i++) /* For each possible grade */
        {
            ypos = (2*i+1)*DrawingArea_height/(2*num_grades+1);
            /* Write the grade on the left side of the drawing area */
            XDrawString (this_display, DrawingArea_window,
                    DrawingArea_GC,
                    20, ypos + height_rect, grades[i],
                    strlen(grades[i]));
            /* Draw horizontal bar to represent students with that
                    grade */
            XFillRectangle (this_display, DrawingArea_window,
                    DrawingArea_GC, 50, ypos, (DrawingArea_width -
                    90)* results[i]/highest, height_rect);
            /* Write no of students on the right side of the drawing
                    area */
            XDrawString (this_display, DrawingArea_window,
                    DrawingArea_GC,
                    DrawingArea_width - 30, ypos + height_rect,
                    results_str[i], strlen(results_str[i]));
        }
        XFlush (this_display);
    }

The next() routine is executed when the Next Subject pushbutton is activated. It changes the query to get the results for the next subject, calls get_results() to retrieve the results, and calls draw_chart() to display the results.

    /*** Display results for the next subject ***/

    static  void     next (obj, param)
            object   obj;
            char*    param;
    {
        /* Formulate the query to get the results for next subject */
        if (++current_subject == num_subjects)
            current_subject = 0;
        sprintf (query, Select_Results, subjects[current_subject]);
        EXEC SQL PREPARE s_results FROM :query;
        get_results();
        draw_chart();
    }

Finally, the exit_action() routine cleans up the embedded SQL data structures before the module is terminated.

    /*** Cleanup SQL data structures before exiting ***/

    static  void     exit_action (obj, param)
            object   obj;
            char*    param;
    {
        EXEC SQL EXIT_SQL;
    }



7.9 Example 5: FlatFileApp

File: $EMPRESSPATH/rdbms/gui/examples/ex5_actn.c
Application Name: FlatFileApp
Module Name: FlatFileApp
Description: Reads a flat file and displays the result in a multilist.
Concepts Illustrated: Reading flat files, Getting objects, Use of mulitlist 
 
 
Since the application only has one action, everything is done in this action. The flat file is read and the multilist arrays are correctly filled. Finally the data is displayed in the multilist.

/***********************************************************************
*     (c) Copyright Empress Software Inc. 1983, 2006
***********************************************************************/

#include <guicc.h>
#include <mscc.h>

#define     CURRENT_MODULE     "FlatFileApp"

#define     ACTION_TBL_NAME     FlatFile_action_table

#define SEPCHARS     "\t\n"     /* list of separation characters */
                                /* in the flat file */

static void         mlist_setup ();

action_tab_entry    ACTION_TBL_NAME[] =
{
{ "enter_mlist",    mlist_setup },
{ CHARNIL }
};

This function reads in a flat file, one record at a time. It then splits the record to correspond to the number of columns of the multilist and store it in appropriate arrays. Finally, it calls the C API functions to display the data in the multilist.

static  void     mlist_setup (obj, param)
        object   obj;
        char*    param;
{

    FILE *flatfile;             /* file pointer to the flat file */
    char *filename="flatfile";  /* name of the flat file */
    char *records[256];         /* array of records. In this case */
                                /* 256 is the maximum number of
                                    records */
                                /* that can be read */
    char *listval0[256];        /* four arrays corresponding to the
                                    four */
    char *listval1[256];        /* columns of the multi list */
    char *listval2[256];
    char *listval3[256];
    char *s;                    /* pointer to indicate when fgets
                                    finishes */
    int num_rec;                /* actual number of records read */
    int i=0, j=0;               /* loop counters */

    object      mainwin;
    object      mlistobject;
    gui_status  status;

    Open the flatfile with appropriate error checking.

    flatfile = fopen(filename,"r");
    if (flatfile == NULL)
    {
        fprintf(stderr,"Cannot open the %s\n", filename);
        exit (0);
    }

    /* allocate memory for each of the records */
    for ( i = 0; i < 256; i++ )
        records[i] = (char *) malloc ( 80 * sizeof(char) );

Read one line ( record in this case ) at a time till the end of the file is reached. Upper limit of number of records that can be read in this case is set to be 256. Read each record and split it into four elements to correspond to the four columns of multilist and store it into appropriate arrays.

    j = 0;

    while ( s != NULL )
    {
        s = fgets ( records[j], 80, flatfile );
        listval0[j] = (addr ) strtok (records[j], SEPCHARS);
        listval1[j] = (addr ) strtok (NULL, SEPCHARS);
        listval2[j] = (addr ) strtok (NULL, SEPCHARS);
        listval3[j] = (addr ) strtok (NULL, SEPCHARS);
        j++;
    }

    /* set the number of records read from the flat file */
    num_rec = j 1;

After getting the data from the flat file in appropriate errors, the main window and the multilist is obtained.

    status = empgui_c_bin_get_by_name("MAINWIN",&mainwin);
    if (status != E_C_SUCCESS) {
        fprintf (stderr, "enter error 10: %d\n", status);
    }

    status = empgui_c_obj_get_by_name (mainwin, "multilist",
                                            &mlistobject);
    if (status != E_C_SUCCESS) {
        fprintf (stderr, "enter error 20: %d\n", status);
    }

Finally the arrays filled with data from the flat file is displayed in the multilist.

    status = empgui_c_list_set_items (mlistobject, num_rec, listval0,
                                    listval1, listval2, listval3, 0);
    if (status != E_C_SUCCESS) {
        fprintf (stderr, "enter error 20: %d\n", status);
    }
}