_C PROGRAMMING_
by
Al Stevens

Listing 1.


/* ----------- menu.h ---------- */

typedef struct w_menu {
    char *mname;            /* menu bar selection names                     */
    char *mhlpmsg;          /* menu bar prompting messages                  */
    char **mselcs;          /* the pop-down menu selections                 */
    char **mshelp;          /* help mnemonics for the pop-down selections   */
    char *mskeys;           /* key strokes that accompany the selections    */
    int (**func)(int,int);  /* the functions to execute for the selections  */
    int lastvsel;           /* most recent vertical selection               */
} MENU;

void menu_select(MENU *, int);
char *display_menubar(MENU *);
void restore_menubar(char *);

Listing 2.

/* ------------ menu.c ------------ */

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "window.h"
#include "menu.h"

#define ON 1
#define OFF 0

static int getvmn(void);
static void haccent(int);
static void dimension(char **,int *,int *);
static void light(int);
static int vlook(int,int);

int hsel = 1;        /* horizontal selection */
MENU *mn;            /* active menu          */

/* ------------- display & process a menu ----------- */
void menu_select(MENU *mnn, int sel)
{
    int hs, sx, sy, vsel, holdhsel, frtn = FALSE;
    char *mb;

    if (sel)
        hsel = sel;
    mn = mnn;
    sx = wherex();
    sy = wherey();
    mb = display_menubar(mn);
    light(ON);
    while (!frtn && ((vsel = getvmn()) != 0))    {
        light(OFF);
        holdhsel = hsel;
        hs = hsel;
        hsel = 1;
        frtn = (*(mn+hs-1)->func [vsel-1]) ?
               (*(mn+hs-1)->func [vsel-1])(hs,vsel) : FALSE;
        hsel = holdhsel;
        mn = mnn;
        light(ON);
    }
    light(OFF);
    gotoxy(sx, sy);
    restore_menubar(mb);
}

/* --- display the menu bar with no selections chosen ---- */
char *display_menubar(MENU *mn)
{
    int i = 0;
    char *mb;

    if ((mb = malloc(160)) != NULL)
        gettext(1,1,80,1,mb);
    window(1,1,80,25);
    gotoxy(1,1);
    textcolor(MENUFG);
    textbackground(MENUBG);
    cprintf("    ");
    while ((mn+i)->mname)
        cprintf(" %-10.10s ", (mn+i++)->mname);
    while (i++ < 6)
        cprintf("            ");
    cprintf("    ");
    hidecursor();
    return mb;
}

/* ------------ restore the menu bar line --------------- */
void restore_menubar(char *mb)
{
    if (mb)    {
        puttext(1,1,80,1,mb);
        free(mb);
    }
}

/* ---------pop down a vertical menu --------- */
static int getvmn()
{
    int ht, wd, vx, sel;

    while (TRUE)    {
        dimension((mn+hsel-1)->mselcs, &ht, &wd);
        if (!(mn+hsel-1)->lastvsel)
            (mn+hsel-1)->lastvsel = 1;
        if (ht > 0)    {
            vx = 5+(hsel-1)*12;
            establish_window(vx, 2, vx+wd+1, ht+3,
                MENUFG, MENUBG,TRUE);
            text_window((mn+hsel-1)->mselcs, 1);
            sel = select_window((mn+hsel-1)->lastvsel,
                SELECTFG, SELECTBG, vlook);
            delete_window();
            if (sel == FWD || sel == BS)
                haccent(sel);
            else
                return ((mn+hsel-1)->lastvsel = sel);
        }
        else    {
            if ((sel = getkey()) == *(mn+hsel-1)->mskeys)
                return 1;
            switch (sel)    {
                case FWD:
                case BS:    haccent(sel);
                            break;
                case '\r':    return 1;
                case ESC:    return 0;
                default:    putch(BELL);
                            break;
            }
        }
    }
}

/* ---- if vertical menu user types FWD, BS,
               or a menu key, return it ---- */
static int vlook(ch, sel)
{
    char *cs, *ks;

    if (ch == FWD || ch == BS)    {
        (mn+hsel-1)->lastvsel = sel;
        return ch;
    }
    ks = (mn+hsel-1)->mskeys;
    if ((cs = memchr(ks, tolower(ch), strlen(ks))) == NULL)
        return ERROR;
    return ((mn+hsel-1)->lastvsel = cs-ks+1);
}

/* ----- manage the horizontal menu selection accent ----- */
static void haccent(int sel)
{
    switch (sel)    {
        case FWD:
            light(OFF);
            if ((mn+hsel)->mname)
                hsel++;
            else
                hsel = 1;
            light(ON);
            break;
        case BS:
            light(OFF);
            if (hsel == 1)
                while ((mn+hsel)->mname)
                    hsel++;
            else
                --hsel;
            light(ON);
            break;
        default:
            break;
    }
}

/* ---------- compute a menu's height & width --------- */
static void dimension(char *sl[], int *ht, int *wd)
{
    *ht = *wd = 0;

    while (sl && sl [*ht])    {
        *wd = max(*wd, (unsigned) strlen(sl [*ht]));
        (*ht)++;
    }
}

/* --------- accent a horizontal menu selection ---------- */
static void light(int onoff)
{
    extern struct wn wkw;
    extern char spaces[];
    int ln;

    window(1,1,80,25);
     textcolor(onoff ? SELECTFG : MENUFG);
     textbackground(onoff ? SELECTBG : MENUBG);
     gotoxy((hsel-1)*12+6, 1);
    cprintf((mn+hsel-1)->mname);
    textcolor(TEXTFG);
    textbackground(TEXTBG);
    if ((mn+hsel-1)->mhlpmsg)    {
        if (onoff)    {
            ln = strlen((mn+hsel-1)->mhlpmsg);
            gotoxy((80-ln)/2,25);
            cprintf((mn+hsel-1)->mhlpmsg);
        }
        else    {
            gotoxy(1,25);
            cprintf(spaces);
        }
    }
    current_window();
    hidecursor();
}


Listing 3.


/* --------- testmenu.c ------------ */
#include <stdio.h>
#include <string.h>
#include "window.h"
#include "menu.h"

/* ---------- menu tables --------- */
static char *fselcs[] = {
    "Load",
    "Save",
    "New",
    "Quit   [Esc]",
    NULL
};

static char *eselcs[] = {
    "Move       [F3]",
    "Copy       [F4]",
    "Delete     [F8]",
    "Find       [F7]",
    NULL
};

static char *oselcs[] = {
    "Auto Paragraph Reformat       ",
    "Insert  [Ins]                 ",
    NULL
};

static int quit(int,int);
static int reform(int, int);
static int insert(int, int);
static void set_toggles(void);

static char forced[] = {F3,F4,F8,F7};

static char options[] = {'a', INS};

static int (*ffuncs[])()={NULL,NULL,NULL,quit};
static int (*efuncs[])()={NULL,NULL,NULL,NULL};
static int (*ofuncs[])()={reform,insert};

static char filehelp[] =
   "Load a file, Save current buffer, Type a new file, Quit";
static char edithelp[] =
   "Move, Copy, Delete blocks, Find a string";
static char optnhelp[] = 
   "Toggle editor options";

MENU emn [] = {
    {"File",    filehelp, fselcs, NULL,   "",      ffuncs, 0},
    {"Edit",    edithelp, eselcs, NULL,   forced,  efuncs, 0},
    {"Options", optnhelp, oselcs, NULL,   options, ofuncs, 0},
    {NULL}
};

void main()
{
    set_toggles();
    clear_screen();
    menu_select(emn, 1);
    clear_screen();
}

/* ---- illustrates toggled menu mode selectors -------- */
int reforming, inserting;  /* these are mode variables */

static int reform(hs,vs)
{
    reforming ^= TRUE;
    set_toggles();
    return FALSE;
}

static int insert(hs,vs)
{
    inserting ^= TRUE;
    set_toggles();
    return FALSE;
}

static void set_toggles()
{
    strcpy(&oselcs[0][24], reforming ? "(on) " : "(off)");
    strcpy(&oselcs[1][24], inserting ? "(on) " : "(off)");
}

/* ---- when TRUE is returned, the menu system exits ----- */
static int quit(hs,vs)
{
    return TRUE;
}


Listing 4.


/* --------- entry.h ---------- */

typedef struct field {      /* data entry field description*/
    int frow;               /* field row                   */
    int fcol;               /* field column position       */
    int fx;                 /* running column              */
    char *fbuff;            /* field buffer                */
    char *fmask;            /* field data entry mask       */
    char *fhelp;            /* field help window mnemonic  */
} FIELD;

void field_tally(void);
int data_entry(FIELD *, int, int);
void clear_template(void);
void insert_line(void);

#define INSERTING TRUE      /* initial Insert mode */



Listing 5.

/* --------- entry.c ---------- */

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <dos.h>
#include "window.h"
#include "entry.h"

#define FIELDCHAR '_'
int inserting = INSERTING;      /* insert mode, TRUE/FALSE */

extern struct wn wkw;

/* -------- local prototypes -------- */
static void addfield(FIELD *);
static void disp_field(FIELD *, char *, char *);
static int read_field(int);
static void data_value(FIELD *);
static int endstroke(int);
static void home_cursor(void);
static int backspace(void);
static void end_cursor(void);
static void forward(void);
static int fore_word(void);
static int back_word(void);
static void delete_char(void);
static void delete_word(void);

static FIELD *fhead;
static FIELD *ftail;
FIELD *fld;

/* -------- display a data field ------ */
static void disp_field(FIELD *fldv, char *bf, char *msk)
{
    char cl[80], *cp = cl;

    while (*msk)    {
        *cp++ = (*msk != FIELDCHAR ? *msk : *bf++);
        msk++;
    }
    *cp = '\0';
    writeline(fldv->fx + fldv->fcol - 1,
               fldv->frow, cl);
}

/* ------- display the data value in a field ------ */
static void data_value(FIELD *fldv)
{
    gotoxy(fldv->fcol, fldv->frow);
    fldv->fx = 1;
    disp_field(fldv, fldv->fbuff, fldv->fmask);
}

/* ------ display all the fields in a window ------- */
void field_tally()
{
    FIELD *fldt;

    fldt = fhead;
    while (fldt != ftail)    {
        data_value(fldt);
        fldt++;
    }
}

/* ------- clear a template to all blanks ------ */
void clear_template()
{
    FIELD *fldc;
    char *bf, *msk;

    fldc = fhead;
    while (fldc != ftail)    {
        bf = fldc->fbuff;
        msk = fldc->fmask;
        while (*msk)    {
            if (*msk == FIELDCHAR)
                *bf++ = ' ';
            msk++;
        }
        fldc++;
    }
    *bf = '\0';
    field_tally();
}

char *mask, *buff;

/* ------- read a field from the keyboard ------------- */
static int read_field(c)
{
    int done = FALSE, first = TRUE;

    home_cursor();
    while (TRUE)    {
        gotoxy(fld->fx+fld->fcol-1, fld->frow);
        if (!c || !first)
            c = getkey();
        first = FALSE;
        switch (c)    {
            case CTRL_D:
                delete_word();
                break;
            case CTRL_FWD:
                done = !fore_word();
                break;
            case CTRL_BS:
                done = !back_word();
                break;
            case HOME:
                fld->fx = 1;
                home_cursor();
                break;
            case END:
                end_cursor();
                break;
            case FWD:
                forward();
                break;
            case BS:
                backspace();
                break;
            case '\b':
                if (!backspace())
                    break;
                gotoxy(fld->fx+fld->fcol-1, fld->frow);
            case DEL:
                delete_char();
                disp_field(fld, buff, mask);
                break;
            case INS:
                inserting ^= TRUE;
                insert_line();
                break;
            default:
                if (endstroke(c))    {
                    done = TRUE;
                    break;
                }
                if (inserting)    {
                    memmove(buff+1, buff, strlen(buff)-1);
                    disp_field(fld, buff, mask);
                    gotoxy(fld->fcol+fld->fx-1, fld->frow);
                }
                *buff = (char) c;
                putch(c);
                forward();
                if (!*mask)
                    c = FWD;
                break;
        }
        if (done || !*mask)
            break;
    }
    wkw.wx = fld->fx+1;
    return c;
}

/* -------- home the cursor in the field -------- */
static void home_cursor()
{
    buff = fld->fbuff+fld->fx-1;
    mask = fld->fmask+fld->fx-1;
    while (*mask != FIELDCHAR)    {
        fld->fx++;
        mask++;
    }
}

/* ------- move the cursor to the end of the field ------ */
static void end_cursor()
{
    fld->fx += strlen(mask)-1;
    buff += strlen(buff)-1;
    mask += strlen(mask)-1;
    while (*buff == ' ')
        if (!backspace())
            break;
    forward();
}

/* ------ move the cursor forward one character -------- */
static void forward()
{
    do    {
        fld->fx++;
        mask++;
    } while (*mask && *mask != FIELDCHAR);
    buff++;
}

/* ------- move back one character ------------ */
static int backspace()
{
    if (buff != fld->fbuff)    {
        --buff;
        do    {
            --mask;
            --(fld->fx);
        } while (*mask != FIELDCHAR);
        return TRUE;
    }
    return FALSE;
}

/* ------- move forward one word ------- */
static int fore_word()
{
    int ct = 2, test = *buff == ' ';

    while (ct--)    {
        while ((*buff == ' ') == test && *mask)
            forward();
        if (!*mask)
            return FALSE;
        if (test)
            break;
        test = !test;
    }
    return TRUE;
}

/* ------- move backward one word ------- */
static int back_word()
{
    int test;

    if (buff == fld->fbuff)
        return FALSE;
    if (*(buff-1) == ' ')
        backspace();
    test = *buff == ' ';
    while ((*buff == ' ') == test && buff != fld->fbuff)
        backspace();
    if (test)
        while (*buff != ' ' && buff != fld->fbuff)
            backspace();
    if (*buff == ' ')
        forward();
    return TRUE;
}

/* --------- delete a character ----------- */
static void delete_char()
{
    memmove(buff, buff+1, strlen(buff));
    *(buff+strlen(buff)) = ' ';
}

/* ----------- delete a word ---------- */
static void delete_word()
{
    int test = *buff == ' ';
    int ln = strlen(buff);

    while ((*buff == ' ') == test && ln--)
        delete_char();
    if (!test)
        delete_char();
    disp_field(fld, buff, mask);
}

/* ---------- test c for an ending keystroke ----------- */
static int endstroke(int c)
{
    switch (c)    {
        case '\r':
        case '\n':
        case '\t':
        case ESC:
        case F1:
        case F2:
        case F3:
        case F4:
        case F5:
        case F6:
        case F7:
        case F8:
        case F9:
        case F10:
        case PGUP:
        case PGDN:
        case HOME:
        case END:
        case CTRL_FWD:
        case CTRL_BS:
        case CTRL_HOME:
        case CTRL_END:
        case UP:
        case DN:
            return TRUE;
        default:
            return FALSE;
    }
}

/* ----- Process data entry for a screen template. ---- */
int data_entry(FIELD *fldin, int init, int firstfld)
{
    int exitcode = 0, done = FALSE;

    insert_line();
    fhead = ftail = fld = fldin;
    while (ftail->frow)
        ftail++;
    while (firstfld-- && fld != ftail)
        fld++;
    --fld;
    if (init)
        clear_template();
    field_tally();
    /* ---- collect data from keyboard into screen ---- */
    while (done == FALSE)    {
        gotoxy(fld->fcol, fld->frow);
        textcolor(FIELDFG);
        textbackground(FIELDBG);
        data_value(fld);
        gotoxy(fld->fcol, fld->frow);
        fld->fx = 1;
        exitcode = read_field(0);
        textcolor(ENTRYFG);
        textbackground(ENTRYBG);
        data_value(fld);
        switch (exitcode)    {
            case DN:
            case '\r':
            case '\t':
            case CTRL_FWD:
            case FWD:    done = (ftail == fhead+1);
                        fld++;
                        if (fld == ftail)
                            fld = fhead;
                        break;
            case CTRL_BS:
            case UP:    if (fld == fhead)
                            fld = ftail;
                        --fld;
                        break;
            case CTRL_HOME:
                        fld = fhead;
                        break;
            case CTRL_END:
                        fld = ftail-1;
                        break;
            default:    done = endstroke(exitcode);
                        break;
        }
    }
    fld = NULL;
    return (exitcode);
}

/* ---------- set insert/exchange cursor shape ----------- */
void insert_line()
{
    set_cursor_type(inserting ? 0x0106 : 0x0607);
}


Listing 6.

/* ----------- testentr.c --------- */
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include "window.h"
#include "entry.h"

struct config {
    char name[36];
    char acctno[21];
    char password[11];
    char phone[15];
} cfg;

static char mask[] = "___________________________________";
static char phmask[] = "(___) ___-____ ext.____";

FIELD pers_template[] = {
    {3, 14, 1, cfg.name,    mask,    NULL},
    {4, 14, 1, cfg.acctno,  mask+15, NULL},
    {5, 14, 1, cfg.password,mask+25, NULL},
    {6, 14, 1, cfg.phone,   phmask,  NULL},
    {0}
};

void main()
{
    establish_window(15,5,65,12,ENTRYFG,ENTRYBG,TRUE);
    window_title(" Personal Data ");
    gotoxy(3,3);
    cputs("Name:");
    gotoxy(3,4);
    cputs("Account #:");
    gotoxy(3,5);
    cputs("Password:");
    gotoxy(3,6);
    cputs("Phone:");
    data_entry(pers_template, TRUE, 1);
    delete_window();
    return FALSE;
}

