Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

More C++ and a Step Up to ANSI C


OCT89: C PROGRAMMING

Our collection of C++ tools began last month with a simple window manager. The first class we built was a Window that pops up when it is declared from within a program and pops down when it goes out of scope. The Window methods include the following:

The Window class became the base class for three simple derived classes, the YesNo window, the Notice window, and the Error window. Those classes illustrated class inheritance as supported by C++. This month we will look more closely at inheritance with a popdown menu class that is derived from the Window. Then we will introduce a string data type that resembles the strings of Basic.

Menu Classes

To continue our exploration of C++, we will build classes that implement two kinds of menus: The sliding bar menu similar to those used by programs such as Lotus 1-2-3, and the popdown menu similar to the ones that SideKick uses. The sliding bar menu class, named "SlideBar," is its own independent class. The popdown menu class, called "PopdownMenu," is derived from the Window class. These menus are similar to ones we developed in traditional C last year for the TWRP and SMALLCOM projects.

A class is a new data type, described by the programmer to extend the language. When we built the Window and its derived classes, we effectively added a data type. with that addition, our C++ programs can have chars, ints, longs, floats, doubles, structs, unions, Windows, Notices, Errors, and YesNos. Next we will add SlideBars and PopdownMenus. A derived class inherits all the characteristics of the base class from which it is derived and adds its own private and public parts.

Our SlideBar and PopdownMenu classes perform operations similar to one another but with different menu formats. When you declare either class, a menu is created and displayed, and the user is prompted to make a selection. Depending on the user's selection, an applications function is selected from ones that you associate with the selections when you declare the class. Let's look first at the SlideBar class.

Sliding Bar Menus -- When you declare a SlideBar variable, you specify the screen line where the menu will appear, the text of the selections, the first selection to be highlighted by a selection cursor, and pointers to the functions associated with the selections. As soon as you declare the SlideBar, its constructor function displays the menu and prompts the user to make a selection. The user can move the selection cursor back and forth by pressing the right and left arrow keys and can make a selection by pressing the Enter key when the cursor is on the desired selection. The user can also make a selection by pressing the first letter of the selection's name. This convention requires that you assign selection names with unique first letters when you design the menu. When the user makes a selection, the associated function executes.

When you declare one of these menu classes, your program remains in the member functions and in the applications functions associated with selections until the constructor function returns. The constructor function calls the private dispatch member function to manage user selections and dispatch your applications functions. The SlideBar class has a terminate member function that the applications programs call to tell the dispatch function to terminate menu processing. The statement in your program that follows the SlideBar declaration will then execute, but the menu will remain visible until the SlideBar goes out of scope.

A dispatched applications function can use the current_selection member function to determine which vertical selection on the menu caused its dispatch.

Pop Down Menus -- Next let's consider the PopdownMenu class. It is similar in operation to the SlideBar class, but the menu takes the form of a window and is, therefore, derived from the Window class. When you declare the PopdownMenu, you specify the column and row coordinates where the upper left corner of the PopdownMenu window displays. The other initialization parameters are the same as those of the SlideBar menu, and the operation is similar. With the PopdownMenu, however, the user moves the selection cursor up and down rather than to the right and left.

Besides the terminate and current_selection member functions, which work like those of the SlideBar class, the PopdownMenu class includes additional features. A PopdownMenu can have selections that are selectively disabled. This means they are displayed but not available for selection. They have a unique color scheme, and the selection cursor passes over them when the user moves it up and down. The disable_selection and enable_selection public member functions allow the applications code to disable and enable a specified menu selection.

PopdownMenus also support toggle selections, ones that change a binary state switch but do not have associated applications member functions to dispatch. They display a check mark next to their name if the toggle is on and no check mark if the toggle is off. The test_toggle public member function allows an application to test the current value of a selection's toggle.

The definitions of the SlideBar and PopdownMenu classes appear in Listing One menus.h. A program that will use either of these classes will include this file. Along with the class definitions are definitions of the color schemes for menus. The Window class from last month provides for the initialization and changing of a Window's colors. The menu classes, however, assume that a program uses a consistent color scheme for all menus and does not require you to identify the menu colors every time you declare a menu. Listing Two is menus.c, the code that implements the classes. To use them, you must link your program with the object files that you compile from these files. To use the PopdownMenu class, you will need the window.h and window.c files from last month. All the programs in this and last month's columns compile with the Zortech C++ compiler, Version 1.07.

Listings Three and Four are demoslid.c and demopop.c. These programs demonstrate the use of the two menu classes. Demoslid.c defines a SlideBar menu with four selections. Each of the selections executes a function in demoslid.c. The first three of these simulated applications functions declares a Window that the function uses to identify itself. After the user presses a key, the function returns. The fourth function is the Quit selection. It uses a YesNo class (defined in window.h and discussed last month) to ask the user to verify the quit command. If the user says "yes," the function calls the terminate member function to tell the menu to quit.

Demopop.c is similar to demoslid.c, but it demonstrates the additional features available in the PopdownMenu class. It declares a PopdownMenu with five selections. These selections behave in ways that suggest the File menu of an editor program. There are selections to load a file, save a file, and declare a new file. There is a toggle selection named Option. The last selection is the Quit selection.

The Save selection is initially set to be a disabled selection. The minus sign in the text name "-Save" identifies it as such. Whenever you use the Load or New selections, their dispatched functions call the enable_selection public member function to enable the Save selection. When you then use the Save selection, its dispatched function calls the disable_selection function to disable itself.

The Option selection is a toggle. It is implicitly defined as such by the NULL function pointer that the demopop.c program specified for the Option selection's associated applications function. When the user chooses the Option selection, the class automatically inverts the toggle setting. A toggle setting is represented by the appearance or absence of the check mark symbol ('\xfb') as the last character of the selection's name. We use the Option toggle in the Quit function to see if we need to use a YesNo class to verify the Quit request. If the toggle is on, we do not ask the user for verification. This usage serves to illustrate the mechanics of toggle selections.

By combining the SlideBar class with a series of PopdownMenu classes, you could build a menu system where the sliding bar is at the top of the screen with popdown menus under each sliding bar selection. This is the kind of menu system used by many programs. In my development of the two menu classes, I attempted to go the next logical step and develop those traditional sliding bar/popdown menus. The wall I ran into was either the limit of Zortech C++ or my own inexperience with the C++ language. A two-dimensional menu driver needs pointers to arrays of function pointers, or something similar. I was not able to get these constructs working within the realm of the new operator or as parameters to overloaded constructor functions. It is also not clear to me how the variable argument list feature of C++ and ANSI C fit into the overloaded function construct. These issues are typical of the ones you and I will encounter as we try to use C++ in ways that our imaginations move us, and, when and if I solve them, I will share the solutions.

The String Class

When I migrated from Basic to C, I mourned the loss of the string variable. K&R reassured me that character arrays and standard functions using character pointers would, with care, serve the same purpose, but I missed the old way, wanting ever since to be able to say this in a C program:

if (username == "Wynton")
    username = username + " Marsalis";

or better yet:

username += " Marsalis";

With C, we must use the strcat function to perform such a concatenation, and the receiving string must be long enough to receive the added value. There are other useful string operations in Basic, and I've been wanting them in C for a long time.

Well, want no more. C++ brings that capability to the C language. It does, that is, if you roll your own string class, and that is just what we are about to do.

Listing Five is strings.h, the header file that describes the new class, named "string." Listing Six is strings.c, the code that implements the string class.

The string has one private part, a character pointer. When you declare the string, the constructor function initializes the pointer. All operations on the string use this pointer. There are four constructor functions for the string class, supporting the four different ways you can declare a string and illustrating the C++ technique for overloading constructor functions.

C++ lets you overload functions. This means that several functions can have the same name but different parameter types. The language translator decides which function you are calling based on the parameters you are passing. (If C had this feature, we would not need a strcpy function and a strncpy function, for example. One function could handle both operations.) Function overloading does, however, mandate the use of prototypes, and that is a good requirement.

The four ways of declaring a string are shown here:

string name 1;         // null string
string name2("George");   // character pointer
string name3(name2);    // another string
string name4(80);       // length

Each of these declarations will establish a string with a pointer to the appropriate character array. The name 1 string will contain a pointer to a null string. The other three strings will contain pointers to character arrays. All four constructors use string space taken from the free store (the C++ heap) with the C++ new operator. Each constructor makes a new copy of the string value rather than simply pointing to the initializing value. The name4 string points to an array of 81 characters all with a zero value.

String Assignments -- Once you declare a string, there are a number of operations you can perform on it. First let's consider the assignment operator. Once you establish a string variable, you can assign either a character array or another string to it as shown here.

string oldstring;         // declare two strings
string newstring;
oldstring = "Hello, Dolly",      // assign an array
newstring = oldstring;        // assign a string

These two assignments are achieved with the operator= overloaded functions that have a character pointer and a string as their parameters. In C++ you can overload the unary and binary C operators to work with classes in ways you design. You cannot change the way the operators work with standard C data types, you cannot create operators that do not exist in C, you cannot use unary operators as binary ones, and so forth. Your use of operator overloading must be done in ways that make sense to the language translator's lexical scan and parser. We'll look at more operator over-loading later.

The stradr Function -- The stradr public member function returns the address of the string. It is defined as a const char * function, which means that it returns a pointer to something that cannot be modified. The intention here is to prevent an applications program from changing the value of a string through the stradr function. All changes to strings should be made with the string operations described soon. This intention is not always realized. Zortech C++ does not disallow you from assigning the value returned from the function to a regular character pointer. It does prevent you from using the function call in an expression that would change the string. For example, the following code is OK according to Zortech:

    string myname("Joe")
    char *cp = myname.stradr();
    *cp = 'M';

The compiler should warn you that the second statement is assigning a pointer-to-const to a regular pointer, but it does not. Experiments with Turbo C and Microsoft C reveal that they do issue such warnings (in a C context, of course). See the ANSI Corner later for more discussion on this circumstance as it relates to C, not C++.

Zortech will issue an error if you try this code:

     string myname("Joe");
    *myname.stradr() = 'M';

The right, left, and mid Functions -- The string class includes three public member functions designed to emulate the RIGHT$, LEFT$, and MID$ substring functions of Basic. These functions return pieces of strings as new strings. For example:

    string name("George Kingfish Stevens");
    string firstname;
    string middlename;
    string lastname;
    firstname = name.left(6);
    middlename = name.mid(8, 8);
    lastname = name.right(7);

This code assigns to the three null strings the three parts of the originally initialized name.

String Concatenation -- The string class includes several ways that you can concatenate strings. Assume that you have strings named newname, name and lastname. These are the ways that you can concatenate strings:

    newname = name + "Smith";
    newname = name + lastname;
    name = name + "Smith";
    name = name + lastname;
    name += "Smith";
    name += lastname;

String concatenation does not require you to assure enough space for the added value. String sizes grow according to their needs.

Relational Operators

Our new string class allows us to make relational tests between strings and between strings and character arrays. You can make the following tests:

     if (name == "Sam")
    if (name < othername)

and any other combination of two strings or one string and a character array where the relational operator is one of these:

==, !=, <, >, <=, >=

The only restriction is that the left side of the test must be a string class rather than an array.

String Subscripts -- You can use the [] subscript operators to read the individual character values of a string in ways similar to how you work with regular C character arrays. For example:

     string name("Otis");
    char ch = name[2];

The ch character variable will receive the 'i'from the string. You cannot, however, do this:

     name[2] = 'e'; // invalid statement

because the value returned by the overloaded [] operator is not a C lvalue.

You can, however, use the overloaded + operator to form an lvalue. The following operations are valid:

     ch = *(name+2);
    *(name+2) = 'e';

The string class represents what I believe to be the real potential for C++, its ability to extend the C language with reusable, generic data types. Perhaps you have no desire to make C look more like Basic with our string class, but the exercise reveals the possibilities that class definition adds to the C language.

The C++ language is still without the large user base that would drive us toward standard conventions. The ANSI C committee has decided not to undertake the standardization of C++. Therefore, not until Borland and Microsoft introduce C++ compilers, complete with integrated development environments and hot-shot debuggers, can PC developers get serious about it. We can only hope that it happens in the not-too-distant future, but there have been no announcements. Until that time, C++ is still a wonderful study in what programming ought to be, and I encourage you to get into it.

The ANSI Corner: const and volatile

The ANSI X3J11 committee submitted their draft proposed standard for the C language to ANSI for approval last spring, but some snags developed, mostly procedural or bureaucratic. Those should be cleared up soon, and a true ANSI C standard should exist, perhaps by the time you read this column.

The draft standard document is not exactly fireside reading. Eventually it will be the final authority on how C should work, but it is neither a tutorial text nor an easily understood reference document. Compiler developers will study it in great detail trying to conform. We, the consumers, must trust our compiler writers to have correctly interpreted and implemented the standard language.

This small section of the DDJ "C Programming" column is a new monthly addition that will address some of the features that the ANSI C standard adds to the C language. Most of these features are already implemented in the C compilers you use now because the compiler writers have been closely following the development of the proposed standard. Each month we will look at another part of the ANSI standard.

The const Type Qualifier -- A const variable is one that your program cannot modify with an assignment or by incrementing or decrementing. You can declare a variable to be a const in one of these ways:

    1. const int i1;

    2. int const i2;

    3. const int *ip1;

    4. int const *ip2;

    5. int *const ip3;

    6. const int *const ip4;

    7. int const *const ip5;

The first two forms declare integers that cannot be changed. The only correct way to put a value into this integer or any other const-qualified variable is with initialization as shown here.

     const int i = 123;

The third and fourth forms are pointers to integers where the integers cannot be changed. The fifth form is a pointer to an integer that can be changed, but the pointer itself cannot be changed. The sixth and seventh forms are pointers that cannot be changed and that point to integers that also cannot be changed.

The const type qualifier is not perfect. There are exceptions to the protection it will provide, and the ANSI document abdicates responsibility for most of them, saying, "If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined." There must have been a good reason for that. The original K&R does not include the const keyword, so the reason cannot be to protect existing, pre-ANSI code.

What does all this mean? What, for example, should happen if you were to initialize a non-const pointer with the address of a const variable as shown here?

     const int i;
    int *ip = &i;

Some compilers courageously give a warning for this code. It should probably be an error, but the wimpy language of the ANSI spec does not provide for that. Call it an error and you do not conform, I think.

Suppose we pass a pointer of the third form above to a function that is expecting (by virtue of its prototype and declaration) a non-const pointer. Consider this:

     const char *ch = "123";
     strcpy(ch, "456");

Once again, some compilers issue warnings. This code should, however, be an error, because the strcpy function is defined to expect a normal pointer as the first parameter, and will, in fact change wherever that pointer points. If you ignore the warning, or use a compiler that does not issue one, the const keyword is worthless in this context.

ANSI specifies "undefined" behavior when const is used along with a function declaration, meaning that the following code may or may not give the desired results:

     const char *myaddr(void);

The ANSI position (or nonposition) would seem to leave open what should happen when one source code file declares an external variable as const and another declares the same variable as non-const. Turbo C, for example, offers no protection for a const variable if another source file leaves out the const-type qualifier. A more appropriate behavior would depend on a linker that knows the type qualification of external variables.

According to ANSI, if a struct declaration includes the const-type qualifier, the structure members are const. Microsoft C conforms to this rule, but Turbo C does not. ANSI's expression of this rule is vague and implicit and probably subject to interpretation.

If an array is const-qualified, that means its elements are const.

The volatile Type Qualifier -- A volatile variable is one that might be modified from an external, asynchronous source, such as an interrupt service routine. Its purpose is to allow compilers to bypass some optimization when handling the variable. For example, you might have a global variable that your program is working with. A hardware interrupt occurs, and the interrupt service routine modifies that variable. If your compiler is unaware that such modifications could occur, the compiled code might be saving the variable in a register or on the stack rather than in its designated memory location. If you qualify the variable as a volatile, the compiler then knows to always keep the value in a location available to external influence. This could have tricky consequences. It might be necessary for the compiled code to disable interrupts whenever it is working with the variable, for example, which could introduce timing problems.

Obviously, the volatile type qualifier has no meaning when applied to an automatic variable. Static variables declared inside functions could be modified by interrupts if the interrupted function is called recursively from the interrupt service routine, so they are subject to the benefits of the volatile qualifier, but automatic variables are usually on the stack or in registers and each recursive call to a function has its own copies of the automatic variables.

A const, volatile Variable -- ANSI provides for a variable that has both the const and the volatile type qualifiers. If you code this statement:

  extern const volatile int x;

the variable exists somewhere else in a way that it can be modified by an interrupt, but the local function cannot modify it.

Availability

All source code for articles in this issue is available on a single disk. To order, send $14.95 (Calif. residents add sales tax) to Dr. Dobb's Journal, 501 Galveston Dr., Redwood City, CA 94063, or call 800-356-2002 (from inside Calif.) or 800-533-4372 (from outside Calif.). Please specify the issue number and format (MS-DOS, Macintosh, Kaypro).

_C Programming_ by Al Stevens

[LISTING ONE]

<a name="021a_000a">


// ------------ menus.h

#ifndef MENUS
#define MENUS

#include "window.h"

#define MAXSELECTIONS 12
#define CHECK '\xfb' // IBM Graphics character set check mark

#define MENUFG CYAN
#define MENUBG BLUE
#define SELECTFG BLACK
#define SELECTBG WHITE
#define DISABLEFG LIGHTGRAY
#define DISABLEBG BLUE

//
//   SLIDING BAR MENUS
//
class SlideBar    {
    int row;                       // menu screen row
    void (**mfunc)(SlideBar&);     // selection functions
    char **mtext;                  // selection titles
    int selections;                // number of selections
    int quitflag;                  // flag for appl to say quit
    unsigned *msave;               // save area for menu bar
    int selection;                 // selection position
    int dispatch(int sel, int titlewidth);
public:
    SlideBar(int line, char **text, int sel,
        void (**func)(SlideBar&));
    ~SlideBar();
    void terminate(void)
        { quitflag = 1; }
    int current_selection(void)
        { return selection; }
};

//
//    POPDOWN MENUS
//
class PopdownMenu : Window    {
    int selections;               // number of selections
    void (**mfunc)(PopdownMenu&); // selection functions
    char **text;                  // address of menu text
    int quitflag;                 // flag for appl to say quit
    int selection;                // current selection position
    // --- private methods
    int get_selection(int sel);
    int dispatch(int sel);
    int menuheight(char **text);
    int menuwidth(char **text);
    void operator<<(char **text);
public:
    PopdownMenu(unsigned left, unsigned top,
            char **text, int sel, void (**func)(PopdownMenu&));
    ~PopdownMenu(){};
    void terminate(void)
        { quitflag = 1; }
    void disable_selection(int sel)

        { *text[sel-1] = '-'; }
    void enable_selection(int sel)
        { *text[sel-1] = ' '; }
    int test_toggle(int selection);
    int current_selection(void)
        { return selection; }
};

#endif






<a name="021a_000b"><a name="021a_000b">
<a name="021a_000c">
[LISTING TWO]
<a name="021a_000c">

// ----------- menus.c

#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <conio.h>
#include "menus.h"
#include "console.h"

static void select(int row,int sel, char *ttl,int set,int wd);
#define ON  1
#define OFF 0

//
//   SLIDING BAR MENUS
//

// ----------- constructor for a sliding menu bar
SlideBar::SlideBar(int line, char **text, int sel,
        void (**func)(SlideBar&))
{
    savecursor();
    hidecursor();
    initconsole();
    // ------ menu variables
    quitflag = 0;
    mfunc = func;
    mtext = text;
    // -------- save video memory
    msave = new unsigned[SCREENWIDTH];
    row = min(line-1, 24);
    savevideo(msave, row, 0, row, SCREENWIDTH-1);
    // --------- display the menu bar
    colors(MENUFG, MENUBG);
    setcursor(0, row);
    int cols = SCREENWIDTH;
    while (cols--)
        window_putc(' ');
    // ---- compute the width of the selection texts
    int titlewidth = 0;
    for (int i = 0; mtext[i] && i < MAXSELECTIONS; i++)
        titlewidth = max(titlewidth, strlen(mtext[i]));
    // ----- save the selection count
    selections = i;
    // ------ display the selection texts
    for (i = 0; i < selections; i++)
        select(row, i+1, mtext[i], OFF, titlewidth);
    // ------- dispatch the menu's selections
    dispatch(sel, titlewidth);
}

// ----------- destructor for a menu bar
SlideBar::~SlideBar(void)
{
    restorevideo(msave, row, 0, row, SCREENWIDTH-1);
    delete msave;
    restorecursor();
    unhidecursor();
}

// ------ navigate the menu and dispatch a chosen function
int SlideBar::dispatch(int sel, int titlewidth)
{
    savecursor();
    int sliding = 1;
    if (sel)
        selection = sel;
    while (sliding)    {
        // ---- highlight the menu bar selection
        select(row, selection, mtext[selection-1],
                                        ON,titlewidth);
        // ----- read a selection key
        int c = getkey();
        switch (c)    {
            case ESC:
                // ----- ESC key quits
                sliding = 0;
                break;
            case FWD:
                // ------ right-arrow cursor key
                select(row, selection, mtext[selection-1],OFF,
                    titlewidth);
                if (selection++ == selections)
                    selection = 1;
                break;
            case BS:
                // ------ left-arrow cursor key
                select(row, selection, mtext[selection-1],OFF,
                    titlewidth);
                if (--selection == 0)
                    selection = selections;
                break;
            default:
                // ---- test for 1st letter match
                for (int i = 0; i < selections; i++)
                    if (tolower(c) == tolower(mtext[i][1]))   {
                        // -- match, turn off current selection
                        select(row, selection,
                            mtext[selection-1],
                            OFF,titlewidth);
                        // --- turn on new selection
                        selection = i+1;
                        select(row, selection,
                            mtext[selection-1],
                            ON,titlewidth);
                        break;
                    }
                if (i == selections)
                    break;
            case '\r':
                // ------- ENTER key = user selection
                if (mfunc[selection-1])
                    (*mfunc[selection-1])(*this);
                    sliding = !(quitflag == 1);
                break;
        }
    }
    restorecursor();
    select(row,selection,mtext[selection-1],OFF,titlewidth);
    return quitflag ? 0 : selection;
}

// --- set or clear the highlight on a menu bar selection
static void select(int row,int sel,char *ttl,int set,int wd)
{
    setcursor(5+(sel-1)*wd, row);
    if (set == OFF)
        colors(MENUFG, MENUBG);
    else
        colors(SELECTFG, SELECTBG);
    window_printf(ttl);
}

//
//    POPDOWN MENUS
//

// -------- constructor for the PopdownMenu
PopdownMenu::PopdownMenu(unsigned left, unsigned top,
            char **mtext, int sel, void (**func)(PopdownMenu&))
                : (left, top, left+1+menuwidth(mtext),
                    top+1+menuheight(mtext), MENUFG, MENUBG)
{
    *this << mtext;
    mfunc = func;
    text = mtext;
    selection = sel;
    selections = menuheight(text);
    // ------ dispatch the menu selection
    dispatch(sel);
}

// ------- write text selections into the popdown menu
void PopdownMenu::operator<<(char **mtext)
{
    hidecursor();
    int y = 0;
    // ----- a NULL-terminated array of character pointers
    text = mtext;
    while (*mtext != NULL)    {
        cursor(0, y++);
        char hold = **mtext;
        if (**mtext == '-')    {
            set_colors(DISABLEFG, DISABLEBG);
            **mtext = ' ';
        }
        *this << *mtext;
        **mtext++ = hold;
        set_colors(MENUFG, MENUBG);
    }
    unhidecursor();
}

// ------------ get a popdown menu selection
int PopdownMenu::get_selection(int sel)
{
    // ---- set the initial selection
    if (sel)
        selection = sel;
    int selecting = 1;
    int c;
    while (selecting)    {
        // ------- display the menu's selection texts
        *this << text;
        // ------ watch for disabled selections
        if (**(text+selection-1)=='-')
            c = DN;        // force a key to
                        // bypass a disabled selection
        else    {
            // ---- highlight the current selection
            cursor(0, selection-1);
            set_colors(SELECTFG, SELECTBG);
            *this << *(text + selection - 1);
            set_colors(MENUFG, MENUBG);
            hidecursor();
            c = getkey();    // --- read the next keystroke
        }
        switch (c)    {
            case ESC:
            case FWD:
            case BS:
                // ---- ESC,FWD, or BS will terminate selection
                selecting = 0;
                break;
            case UP:
                // ------- up-arrow cursor key
                do
                    if (--selection == 0)
                        selection = selections;
                while (**(text+selection-1) == '-');
                break;
            case DN:
                // ------- down-arrow cursor key
                do
                    if (selection++ == selections)
                        selection = 1;
                while (**(text+selection-1) == '-');
                break;
            default:
                // ----- other key, test first letter match
                for (int i = 0; i < selections; i++) {
                    if (tolower(c) == tolower(text[i][1]) &&
                             *(text[i]) != '-')   {
                        selection = i+1;
                        selecting = 0;
                    }
                }
                break;
            case '\r':
                // ---- ENTER key is a selection
                selecting = 0;
                break;
        }
    }
    return c == '\r' ? selection : c;
}

// ------------ get and dispatch a popdown menu selection
int PopdownMenu::dispatch(int sel)
{
    int upanddown = 1;
    while (upanddown)    {
          // ---------- read a user selection
        sel = get_selection(sel);
        switch (sel)    {
               // -------- these keys exit the menu
            case FWD:
            case BS:
            case ESC:
                upanddown = 0;
                break;
            default:
                // -------- user has made a menu selection
                if (mfunc[selection-1]) {
                    // ----- execute a menu selection function
                    hidewindow();
                    (*mfunc[selection-1])(*this);
                    upanddown = !(quitflag == 1);
                    restorewindow();
                }
                else    {
                    // ----- no function, must be a toggle
                    char *cp = text[selection-1];
                    cp += strlen(cp)-1;
                    if (*cp == ' ')
                        *cp = CHECK;
                    else if ((*cp & 255) == CHECK)
                        *cp = ' ';
                }
                break;
        }
    }
    return sel == ESC ? ESC : 0;
}

// --------- compute the height of a popdown menu
int PopdownMenu::menuheight(char **text)
{
    int height = 0;
    while (text[height])
        height++;
    return height;
}

// --------- compute the width of a popdown menu
int PopdownMenu::menuwidth(char **text)
{
    int width = 0;
    while (*text)    {
        width = max(width, strlen(*text));
        text++;
    }
    return width;
}

// ----- test the setting of a toggle selection
int PopdownMenu::test_toggle(int sel)
{
    char *cp = text[sel-1];
    cp += strlen(cp)-1;
    return (*cp & 255) == CHECK;
}





<a name="021a_000d"><a name="021a_000d">
<a name="021a_000e">
[LISTING THREE]
<a name="021a_000e">

// ---------- demoslid.c

#include <stddef.h>
#include <conio.h>
#include <stdio.h>
#include "menus.h"
#include "console.h"

// ----------- File menu
static char *fmenu[] = {
    " Load ",
    " Save ",
    " New  ",
    " Quit ",
    NULL
};

static void load(SlideBar&);
static void save(SlideBar&);
static void newfile(SlideBar&);
static void quit(SlideBar&);

static void (*ffuncs[])(SlideBar&)={load,save,newfile,quit};

void main(void)
{
    SlideBar menu(1, fmenu, 1, ffuncs);
}

static void load(SlideBar& menu)
{
    Window wnd(20,10,40,20,BLACK,CYAN);
    wnd.title("(Stub Function)");
    wnd << "\n\n\n\n   LOAD A FILE";
    getkey();
}

static void save(SlideBar &menu)
{
    Window wnd(20,10,40,20,YELLOW,RED);
    wnd.title("(Stub Function)");
    wnd << "\n\n\n\n  SAVE A FILE";
    getkey();
}

static void newfile(SlideBar &menu)
{
    Window wnd(20,10,40,20,YELLOW,RED);
    wnd.title("(Stub Function)");
    wnd << "\n\n\n\n   NEW FILE";
    getkey();
}

static void quit(SlideBar& menu)
{
    YesNo yn("Quit");
    if (yn.answer)
        menu.terminate();
}





<a name="021a_000f"><a name="021a_000f">
<a name="021a_0010">
[LISTING FOUR]
<a name="021a_0010">

// ---------- demopop.c

#include <stddef.h>
#include <conio.h>
#include <stdio.h>
#include "menus.h"
#include "console.h"

// ----------- File menu
static char *fmenu[] = {
    " Load    ",
    "-Save    ",
    " New     ",
    " Option  ",
    " Quit    ",
    NULL
};

static void load(PopdownMenu&);
static void save(PopdownMenu&);
static void newfile(PopdownMenu&);
static void quit(PopdownMenu&);

static void (*ffuncs[])(PopdownMenu&) =
    { load, save, newfile, NULL, quit };

void main(void)
{
    PopdownMenu menu(20, 10, fmenu, 1, ffuncs);
}

static void load(PopdownMenu& menu)
{
    Window wnd(20,10,40,20,BLACK,CYAN);
    wnd.title("(Stub Function)");
    wnd << "\n\n\n\n   LOAD A FILE";
    menu.enable_selection(2);    // enable the save command
    getkey();
}

static void save(PopdownMenu &menu)
{
    Window wnd(20,10,40,20,YELLOW,RED);
    wnd.title("(Stub Function)");
    wnd << "\n\n\n\n  SAVE A FILE";
    menu.disable_selection(2);    // disable the save command
    getkey();
}

static void newfile(PopdownMenu &menu)
{
    Window wnd(20,10,40,20,YELLOW,RED);
    wnd.title("(Stub Function)");
    wnd << "\n\n\n\n   NEW FILE";
    menu.enable_selection(2);    // enable the save command
    getkey();
}

static void quit(PopdownMenu& menu)
{
    if (menu.test_toggle(4))
        menu.terminate();
    else    {
        YesNo yn("Quit");
        if (yn.answer)
            menu.terminate();
    }
}





<a name="021a_0011"><a name="021a_0011">
<a name="021a_0012">
[LISTING FIVE]
<a name="021a_0012">


// -------- strings.h

#ifndef STRINGS
#define STRINGS

#include <string.h>

class string    {
    char *sptr;
public:
    //          CONSTRUCTORS
    // -------- construct a null string
    string(void);
    // ------- construct with a char * initializer
    string(char *s);
    // ------- construct with another string as initializer
    string(string& s);
    // -------- construct with just a size
    string(int len);

    //          DESTRUCTOR
    ~string(void) { delete sptr; }

    //          MEMBER FUNCTIONS
    // ------ return the address of the string
    const char *stradr(void) { return sptr; }

    //          SUBSTRINGS
    // ------ substring: right len chars
    string right(int len);
    // ------ substring: left len chars
    string left(int len);
    // ------ substring: middle len chars starting from where
    string mid(int len, int where);

    //          ASSIGNMENTS
    // -------- assign a char array to a string
    void operator=(char *s);
    // ---------- assign a string to a string
    void operator=(string& s) { *this = s.sptr; }

    //          CONCATENATORS
    // ------- 1st concatenation operator (str1 += char *)
    void operator+=(char *s);
    // ------- 2nd concatenation operator (str1 += str2;)
    void operator+=(string& s) { *this += s.sptr; }
    // ------- 3rd concatenation operator (str1 = str2+char*;)
    string operator+(char *s);
    // ------- 4th concatenation operator (str1 = str2 + str3;)
    string operator+(string& s) { return *this + s.sptr; }

    //          RELATIONAL OPERATORS
    int operator==(string& s) { return strcmp(sptr,s.sptr)==0;}
    int operator!=(string& s) { return strcmp(sptr,s.sptr)!=0;}
    int operator<(string& s)  { return strcmp(sptr,s.sptr)< 0;}
    int operator>(string& s)  { return strcmp(sptr,s.sptr)> 0;}
    int operator<=(string& s) { return strcmp(sptr,s.sptr)<=0;}
    int operator>=(string& s) { return strcmp(sptr,s.sptr)>=0;}
    int operator==(char *s)   { return strcmp(sptr,s)==0; }
    int operator!=(char *s)   { return strcmp(sptr,s)!=0; }
    int operator<(char *s)    { return strcmp(sptr,s)< 0; }
    int operator>(char *s)    { return strcmp(sptr,s)> 0; }
    int operator<=(char *s)   { return strcmp(sptr,s)<=0; }
    int operator>=(char *s)   { return strcmp(sptr,s)>=0; }

    //          SUBSCRIPTORS
    char operator[](int n) { return *(sptr + n); }
    char* operator+(int n) { return sptr + n; }
};

#endif






<a name="021a_0013"><a name="021a_0013">
<a name="021a_0014">
[LISTING SIX]
<a name="021a_0014">

// -------- strings.c

#include <stddef.h>
#include <stream.hpp>
#include "strings.h"

// -------- construct a null string
string::string(void)
{
    sptr = new char;
    *sptr = '\0';
}
// ------- construct with a char * initializer
string::string(char *s)
{
    sptr = new char[strlen(s)+1];
    strcpy(sptr, s);
}
// ------- construct with another string as initializer
string::string(string& s)
{
    sptr = new char[strlen(s.sptr)+1];
    strcpy(sptr, s.sptr);
}
// -------- construct with just a size
string::string(int len)
{
    sptr = new char[len+1];
    memset(sptr, 0, len+1);
}
// -------- assign a char array to a string
void string::operator=(char *s)
{
    delete sptr;
    sptr = new char[strlen(s)+1];
    strcpy(sptr, s);
}
// ------- 1st concatenation operator (str1 += char *;)
void string::operator+=(char *s)
{
    char *sp = new char[strlen(sptr) + strlen(s) + 1];
    strcpy(sp, sptr);
    strcat(sp, s);
    delete sptr;
    sptr = sp;
}
// ------- 3rd concatenation operator (str1 = str2 + char*;)
string string::operator+(char *s)
{
    string tmp(*this);
    tmp += s;
    return tmp;
}
// ------ substring: right len chars
string string::right(int len)
{
    string tmp(sptr + strlen(sptr) - len);
    return tmp;
}
// ------ substring: left len chars
string string::left(int len)
{
    string tmp(len+1);
    strncpy(tmp.stradr(), sptr, len);
    return tmp;
}
// ------ substring: middle len chars starting from where
string string::mid(int len, int where)
{
    string tmp(len+1);
    strncpy(tmp.stradr(),sptr+where-1,len);
    return tmp;
}












Copyright © 1989, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.