C PROGRAMMING

D-Flat Clipboard, Searches, and Pictures

This article contains the following executables: DFLT10.ARC D10TXT.ARC

Al Stevens

This month we introduce a new D-Flat window class, the PICTUREBOX, and provide the code that supports the clipboard and text searching for the EDITBOX class. For those of you who came to this project in the middle, D-Flat is a public domain function C language library that implements the Common User Access (CUA) user interface in text mode on DOS computers. First, though, a travelogue.

Changes in Latitudes

I spent the better part of this week in Key West following Judy around and watching the bicycles, scooters, tourists, and sunsets. When you get away from Duval Street and the endless array of gift shops and restaurants, you can stroll quietly and aimlessly among a motley collection of souls and domiciles in the best climate imaginable. It is November, and The Season hasn't started. There is a gentle sea breeze, the humidity is down, and the temperature is hanging at just around 80. Forget work ethics. This is a haven and a hideout for the formerly overworked. You can forget about home and office. I can't imagine how Truman, Hemingway, or Jimmy Buffet got anything done here. My column is two days late and so the truth is out. I am a closet bum. Even the sight of Judy flashing the MasterCard around town fails to stir me into action. The laptop is in the hotel room, idle. It might as well be at the bottom of that cool, deep, green Gulf of Mexico.

You find time to reflect, though. Thinking doesn't take much motion, and all the bars are outdoors where bars ought to be. Only last week I was at the extreme other end of the forty-eight in Washington State. It was cold and raining. An intense group of young men was telling me about how programming is going to be done soon. We were inside. There wasn't a bar in sight. They are well into this new technology and building some of the tools, and they want me to write about it. They talk fast and with precision. Their waking moments are spent knowing about and shaping things that the dirty, unshaven old guy over there on that ratty old bicycle doesn't know or care about. He's playing a harmonica and talking to his hat. The two ends of knowledge. Maybe these programmers don't know it, but they'd trade places with him for a while. He wouldn't. You can't quite see the Pacific Northwest from Key West. You can't see very far at all.

Thirty-Something

Do you know what a DOS extender is? The difference between 32-bit flat and 32-bit small memory models? How many bytes a gigabyte has? How protected-mode programming really works? Many of you know these things. Many more of you do not. If you are a PC programmer, you will be hearing more about the 32-bit world. It is where DOS should have been a long time ago. The hardware wasn't there. One thing and another kept memory chips and snappy processors out of reach for a while. The cost of sand, they told us. International trade embargos. Now you can get a 20-MHz 386 at the grocery store. The software is going to have to catch up. We already have the C compilers and the DOS extenders. There will be more, and there will be operating environments that make use of all that wasted computer power.

The D-Flat Clipboard

D-Flat has a clipboard that works like the one defined in the CUA specification. A clipboard is a data store into which you cut, copy, and paste information. In a GUI, the clipboard can contain graphics or text. D-Flat is a text-mode system. Therefore, it can store only text in its clipboard.

Users use the clipboard to move data around in an application. You cut or copy a block of text to the clipboard from a marked location in an edit box. Copying the text does just that; it copies the text into the clipboard. Cutting the text copies it and then deletes it from its original location. We discussed marking text blocks last month.

When the clipboard has text in it, the user can paste that text somewhere else in the document. Pasting inserts the text at the current keyboard cursor location just as if the user had retyped it.

Listing One, page 138, is clipbord.c, the code that implements copying and pasting. The editbox.c source file from last month has code that calls the functions in clipbord.c. When the user performs a cut operation, the editbox code uses the clipboard's copy routine and then uses the block delete routine in editbox.c to delete the text block. The Clipboard character pointer in clipbord.c points to the clipboard's text. When that pointer is NULL, the clipboard is empty. When the user has done the first cut or copy, the clipboard then has contents for as long as the program runs. Each new cut or copy replaces the clipboard's current contents. The ClipboardLength variable contains the number of characters in the clipboard.

The CopyToClipboard function copies the marked text into the clipboard. First it computes pointers to the beginning and end of the text block and the block's length. Then it reallocates the clipboard's memory buffer and copies the marked block into the new memory buffer.

The PasteText function inserts the clipboard's text into the editbox at the current cursor location. This function accepts the clipboard's buffer address and text length as parameters rather than from the global Clipboard and ClipboardLength variables because the editbox's undo feature uses the PasteText function to reinsert text into the editbox from the undo buffer. The function first computes a new length for the editbox's buffer by summing the current length of the buffer and the length of the text to be pasted. If the new length is less than the current size of the buffer, the function reallocates the buffer to the new length. Next, it must open a hole in the text to accept the pasted text. The function builds a pointer to the current cursor location and one to that location plus the length of the text to be pasted. Then it shifts the editbox text from the first location to the second using the length of the text from the first location to the end of the buffer. Finally, the PasteText function moves the text to be pasted into the hole that it just created. It marks the text as having been changed and calls the BuildTextPointers function to rebuild the text buffer's text pointer array.

Searching for Text

Listing Two, page 138, is search.c, the code that allows a user to search for and replace string values in an editbox's text. There are three functions that the application program can call. The ReplaceText function uses the ReplaceTextDB dialog box for the user to enter a text string to search for and a text string to replace matches with. The SearchText function uses the SearchTextDB dialog box for the user to enter a text string to search for with no replacement. The SearchNext function assumes that there was a previous call to the SearchText function and continues the search past the point where the previous search found a match. All three functions are called from the application program. In the example Memopad application that I use, the window-processing module for the editbox document windows calls these functions when the user makes the corresponding selection from the application window's Search menu.

All three functions use the common SearchTextBox function, which performs the searches and replacements. The first parameter is the window handle of the edit box being searched. The second parameter is a true/false indicator that tells the function whether or not a replacement is involved. The third parameter is true to tell the function to start at one past the current cursor position and false to tell it to start at the current cursor position. This parameter prevents the function from matching the same string on successive calls from SearchNext. The search algorithm is a simple text scan that compares the text at each location with the search string. The comparison is case-sensitive only when the Check-Case variable is false. Before calling the SearchTextBox function, the program sets this variable based on the setting of a check box in the dialog box. When the program finds a match on the search string, it marks the matching text block in the editbox and positions the keyboard cursor at the first character of the block. If the operation includes a replacement string, the program calls the ReplaceText function to replace the matching text with the specified string. That function will expand or collapse the window's text buffer depending on whether the new text is bigger or smaller than the text it is replacing.

The PICTUREBOX Window Class

The D-Flat PICTUREBOX window class allows you to use the PC's graphics character set to draw horizontal and vertical lines and bars into a text window. This feature supports windows that have custom borders and frames inside the data space. It also lets you draw bar charts. The PICTUREBOX window class records the presence of vectors and bars in the VectorList array, which is an array of VECT structures, each of which defines a vector or bar. A VECT structure contains a RECT structure that defines the dimensions of the vector or bar, and a VectTypes variable that indicates whether the item is a vector or a bar and, if it is a bar, the character that is used to display the bar. A vector is a single line made from the characters that draw single lines. You used those same characters to build window frames. A bar is a line made of the block characters from the graphics character set. There are four different blocks with different textures.

Listing Three, page 138, is pictbox.c, the code that implements the PICTUREBOX class. The applications program interface to a picture box is fairly simple. You create a window of the class and call functions or send messages to draw vectors and bars.

For example, the DRAWVECTOR message draws a horizontal or vertical line on a picture box. It would seem that using the graphics character set to draw a line is a straightforward operation. This would be true if the lines you drew never intersected with other lines you drew. But lines do intersect, and the line-drawing algorithm must deal with those intersections appropriately. That means that as the program is drawing the line, it must look at the character that is being overwritten. If that character is another vector character, the program must draw that element of the new vector with a character that correctly represents the intersection. The substituted character will be different, depending on whether the new vector is horizontal or vertical; whether the vector being intersected is horizontal or vertical, whether the intersecting character is the first, middle, or last character of the new vector; whether the character being intersected is the first, middle, or last character of the intersected vector; and what the intersected vector character is. It might be a simple line character or it might be a corner or other character that resulted from an earlier intersection. The PC's graphics character set includes horizontal and vertical straight lines, a character where horizontal vertical lines intersect forming a cross, the four corners of a box, and four different T formations where a line begins or ends at the intersection of another line.

The PaintVector function in pictbox.c begins with a straight line character, either horizontal or vertical. It then proceeds to write that character into the window for the length of the vector--except when the character it is replacing is itself a vector character. The CharInWnd array contains all the characters that, when intersected, indicate that another vector is being intersected. When that happens, the program must make a substitution. The VectCvt table is an array with four dimensions. Its purpose is to return the substitution character to draw the current element of the new vector. Its first dimension is 0 through 2, depending on whether the intersected vector character is the first, middle, or last character of that vector. The FindVector function computes that subscript by searching the window's VectList array for the last vector in the array that passes through the intersected character position. Then, based on the relative position of the character to the vector, the FindVector function returns the 0 through 2 subscript. The second dimension of the VectCvt array is 0 through 12, depending on which of the 13 vector-drawing characters was intersected. This value is computed from the relative position of the character in the CharInWnd array. The third dimension of the VectCvt array is 0 if the new vector is horizontal and 1 if it is vertical. The last dimension of the array is 0, 1, or 2, depending on whether the intersecting character is the first, middle, or last character of the new vector. Those four subscripts will return the correct character to display from the VectCvt array.

The DrawVector function sends the DRAWVECTOR message, the DrawBox function sends the DRAWBOX message and the DrawBar function sends the DRAWBAR message.

You pass the DrawVector function the window handle, x and y coordinates of the first position of the vector, the vector's length in character positions, and a true/false indicator that is true for a horizontal vector and false for a vertical vector. The DrawVector function sends the DRAWVECTOR message to the window. The message passes a pointer to a RECT structure that defines the vector. The RECT has either a one-character height or width, depending on whether the vector is horizontal or vertical. You could send the message yourself, but the function is easier to work with. Often a function is simpler and easier to understand than sending a message. One of the ways that the Windows SDK evolved was that more and more functions were introduced that took over the drudgery of some of the less-intuitive messages.

The DRAWBOX message expands itself into four DRAWVECTOR messages to draw the four vectors of the box. The DRAWBAR message is similar to the DRAWVECTOR message except that it does not care about intersections.

The Calendar

Listing Four, page 140, is calendar.c, a program that uses the vectors in the PICTUREBOX class to display a calendar. The PICTUREBOX class derives from the TEXTBOX class, so a program can display text as well as vectors and bars. The Calendar function gets the current time and date and creates a PICTUREBOX window to display the calendar. When the window-processing module, CalendarProc, gets the CREATE_WINDOW message, it draws a box around where the days of the month will display and builds the month's 5 x 7 grid by drawing intersecting vectors. The PAINT message displays the dates of the month in the correct grids by calculating the day of the week that the first of the month starts on and filling the grid from there. The program displays the current date in red and the others in black. The KEYBOARD message intercepts the PgUp and PgDn keys to allow the user to page through the calendar a month at a time.

The Bar Chart

Listing Five, page 142, is barchart.c, which displays an example of a bar chart. It begins by creating a PICTUREBOX window and writing some text into the window. To simulate how a program would dynamically build a bar chart from an array of information, the bar chart program uses a simple structure that contains descriptive text and the start and stop positions of the associated bars on the chart. This simple example imitates a project schedule. The start and stop fields are the start and stop months of each project in the array. The program draws a horizontal bar for each project, varying the display character for the bar so that the chart will provide visual separation of the bars. Figure 1 shows the calendar and the bar chart as the example memopad program displays them.

How to Get D-Flat Now

The D-Flat source code is on CompuServe in Library 0 of DDJ Forum and on M&T Online. If you cannot use either online service, send a formatted 360K or 720K diskette and an addressed, stamped diskette mailer to me in care of DDJ, 411 Borel Ave., Suite 100, San Mateo, CA 94402-3522. I'll send you the latest version of D-Flat. The software is free, but if you care to, stick a dollar bill in the mailer for the Brevard County Food Bank. They give help to homeless and hungry families. We've collected about $900 so far from generous D-Flat "careware" users. If you want to discuss D-Flat with me, use CompuServe. My CompuServe ID is 71101,1262, and I monitor DDJ Forum daily.

Just You Wait, 'enry 'iggins

In a letter to the editor, a reader takes me to task for my disapproval of the ANSI committee's coinage of the non-word "stringize." I discuss the matter with Kathy, my teenage niece. She carries the question to a higher authority, her English teacher. Should we accept without issue, as the reader suggests, all new mutations of language and usage, treating them as the natural evolution of communication in a growing society? Kathy reports back, "She went whoa and I'm like hey get out of my face." Pardon me, but I do not know how to punctuate that sentence.



_C PROGRAMMING COLUMN_
by Al Stevens



[LISTING ONE]


/* ----------- clipbord.c ------------ */
#include "dflat.h"

char *Clipboard;
int ClipboardLength;

void CopyToClipboard(WINDOW wnd)
{
    if (TextBlockMarked(wnd))    {
        char *bbl=TextLine(wnd,wnd->BlkBegLine)+wnd->BlkBegCol;
        char *bel=TextLine(wnd,wnd->BlkEndLine)+wnd->BlkEndCol;
        ClipboardLength = (int) (bel - bbl);
        Clipboard = realloc(Clipboard, ClipboardLength);
        if (Clipboard != NULL)
            memmove(Clipboard, bbl, ClipboardLength);
    }
}

void PasteText(WINDOW wnd, char *SaveTo, int len)
{
    if (SaveTo != NULL && len > 0)    {
        int plen = strlen(wnd->text) + len;
        char *bl, *el;

        if (plen > wnd->textlen)    {
            wnd->text = realloc(wnd->text, plen+2);
            wnd->textlen = plen;
        }
        if (wnd->text != NULL)    {
            bl = CurrChar;
            el = bl+len;
            memmove(el, bl, strlen(bl)+1);
            memmove(bl, SaveTo, len);
            BuildTextPointers(wnd);
            wnd->TextChanged = TRUE;
        }
    }
}






[LISTING TWO]


/* ---------------- search.c ------------- */
#include "dflat.h"

extern DBOX SearchTextDB;
extern DBOX ReplaceTextDB;
static int CheckCase = TRUE;

/* - case-insensitive, white-space-normalized char compare - */
static int SearchCmp(int a, int b)
{
    if (b == '\n')
        b = ' ';
    if (CheckCase)
        return a != b;
    return tolower(a) != tolower(b);
}

/* ----- replace a matching block of text ----- */
static void replacetext(WINDOW wnd, char *cp1, DBOX *db)
{
    char *cr = GetEditBoxText(db, ID_REPLACEWITH);
    char *cp = GetEditBoxText(db, ID_SEARCHFOR);
    int oldlen = strlen(cp); /* length of text being replaced */
    int newlen = strlen(cr); /* length of replacing text      */
    int dif;
    if (oldlen < newlen)    {
        /* ---- new text expands text size ---- */
        dif = newlen-oldlen;
        if (wnd->textlen < strlen(wnd->text)+dif)    {
            /* ---- need to reallocate the text buffer ---- */
            int offset = (int)(cp1-wnd->text);
            wnd->textlen += dif;
            wnd->text = realloc(wnd->text, wnd->textlen+2);
            if (wnd->text == NULL)
                return;
            cp1 = wnd->text + offset;
        }
        memmove(cp1+dif, cp1, strlen(cp1)+1);
    }
    else if (oldlen > newlen)    {
        /* ---- new text collapses text size ---- */
        dif = oldlen-newlen;
        memmove(cp1, cp1+dif, strlen(cp1)+1);
    }
    strncpy(cp1, cr, newlen);
}

/* ------- search for the occurrance of a string ------- */
static void SearchTextBox(WINDOW wnd, int Replacing, int incr)
{
    char *s1, *s2, *cp1;
    DBOX *db = Replacing ? &ReplaceTextDB : &SearchTextDB;
    char *cp = GetEditBoxText(db, ID_SEARCHFOR);
    int rpl = TRUE, FoundOne = FALSE;

    while (rpl == TRUE && cp != NULL)    {
        rpl = Replacing ?
                CheckBoxSetting(&ReplaceTextDB, ID_REPLACEALL) :
                FALSE;
        if (TextBlockMarked(wnd))    {
            ClearTextBlock(wnd);
            SendMessage(wnd, PAINT, 0, 0);
        }
        /* search for a match starting at cursor position */
        cp1 = CurrChar;
        if (incr)
            cp1++;    /* start past the last hit */
        /* --- compare at each character position --- */
        while (*cp1)    {
            s1 = cp;
            s2 = cp1;
            while (*s1 && *s1 != '\n')    {
                if (SearchCmp(*s1, *s2))
                    break;
                s1++, s2++;
            }
            if (*s1 == '\0' || *s1 == '\n')
                break;
            cp1++;
        }
        if (*s1 == 0 || *s1 == '\n')    {
            /* ----- match at *cp1 ------- */
            FoundOne = TRUE;

            /* mark a block at beginning of matching text */
            wnd->BlkEndLine = TextLineNumber(wnd, s2);
            wnd->BlkBegLine = TextLineNumber(wnd, cp1);
            if (wnd->BlkEndLine < wnd->BlkBegLine)
                wnd->BlkEndLine = wnd->BlkBegLine;
            wnd->BlkEndCol =
                (int)(s2 - TextLine(wnd, wnd->BlkEndLine));
            wnd->BlkBegCol =
                (int)(cp1 - TextLine(wnd, wnd->BlkBegLine));

            /* position the cursor at the matching text */
            wnd->CurrCol = wnd->BlkBegCol;
            wnd->CurrLine = wnd->BlkBegLine;
            wnd->WndRow = wnd->CurrLine - wnd->wtop;

            /* align the window scroll to matching text */
            if (WndCol > ClientWidth(wnd)-1)
                wnd->wleft = wnd->CurrCol;
            if (wnd->WndRow > ClientHeight(wnd)-1)    {
                wnd->wtop = wnd->CurrLine;
                wnd->WndRow = 0;
            }

            SendMessage(wnd, PAINT, 0, 0);
            SendMessage(wnd, KEYBOARD_CURSOR,
                WndCol, wnd->WndRow);

            if (Replacing)    {
                if (rpl || YesNoBox("Replace the text?"))  {
                    replacetext(wnd, cp1, db);
                    wnd->TextChanged = TRUE;
                    BuildTextPointers(wnd);
                }
                if (rpl)    {
                    incr = TRUE;
                    continue;
                }
                ClearTextBlock(wnd);
                SendMessage(wnd, PAINT, 0, 0);
            }
            return;
        }
        break;
    }
    if (!FoundOne)
        MessageBox("Search/Replace Text", "No match found");
}

/* ------- search for the occurrance of a string,
        replace it with a specified string ------- */
void ReplaceText(WINDOW wnd)
{
    if (CheckCase)
        SetCheckBox(&ReplaceTextDB, ID_MATCHCASE);
    if (DialogBox(wnd, &ReplaceTextDB, TRUE, NULL))    {
        CheckCase=CheckBoxSetting(&ReplaceTextDB,ID_MATCHCASE);
        SearchTextBox(wnd, TRUE, FALSE);
    }
}

/* ------- search for the first occurrance of a string ------ */
void SearchText(WINDOW wnd)
{
    if (CheckCase)
        SetCheckBox(&SearchTextDB, ID_MATCHCASE);
    if (DialogBox(wnd, &SearchTextDB, TRUE, NULL))    {
        CheckCase=CheckBoxSetting(&SearchTextDB,ID_MATCHCASE);
        SearchTextBox(wnd, FALSE, FALSE);
    }
}

/* ------- search for the next occurrance of a string ------- */
void SearchNext(WINDOW wnd)
{
    SearchTextBox(wnd, FALSE, TRUE);
}






[LISTING THREE]


/* -------------- pictbox.c -------------- */

#include "dflat.h"

typedef struct    {
    enum VectTypes vt;
    RECT rc;
} VECT;

unsigned char CharInWnd[] = "D3Z?Y@EC4AB";

unsigned char VectCvt[3][11][2][4] = {
    {   /* --- first character in collision vector --- */
        /* ( drawing D ) ( drawing 3 ) */
             {{"DDD"},     {"ZC@"}},
             {{"ZB?"},     {"333"}},
             {{"ZBB"},     {"ZCC"}},
             {{"???"},     {"???"}},
             {{"YYY"},     {"YYY"}},
             {{"@AA"},     {"CC@"}},
             {{"EEE"},     {"EEE"}},
             {{"CEE"},     {"CCC"}},
             {{"444"},     {"444"}},
             {{"AAA"},     {"AAA"}},
             {{"BBB"},     {"BEE"}}    },
    {   /* --- middle character in collision vector --- */
        /* ( drawing D ) ( drawing 3 ) */
             {{"DDD"},     {"BEA"}},
             {{"CE4"},     {"333"}},
             {{"ZZZ"},     {"ZZZ"}},
             {{"???"},     {"???"}},
             {{"YYY"},     {"YYY"}},
             {{"@@@"},     {"@@@"}},
             {{"EEE"},     {"EEE"}},
             {{"CCC"},     {"CCC"}},
             {{"EE4"},     {"444"}},
             {{"AAA"},     {"EEA"}},
             {{"BBB"},     {"BBB"}}    },
    {   /* --- last character in collision vector --- */
        /* ( drawing D ) ( drawing 3 ) */
             {{"DDD"},     {"?4Y"}},
             {{"@AY"},     {"333"}},
             {{"ZZZ"},     {"ZZZ"}},
             {{"BB?"},     {"?44"}},
             {{"AAY"},     {"44Y"}},
             {{"@@@"},     {"@@@"}},
             {{"EEE"},     {"EEE"}},
             {{"CCC"},     {"CCC"}},
             {{"EE4"},     {"444"}},
             {{"AAA"},     {"EEA"}},
             {{"BBB"},     {"BBB"}}    }
};

/* -- compute whether character is first, middle, or last -- */
static int FindVector(WINDOW wnd, RECT rc, int x, int y)
{
    RECT rcc;
    VECT *vc = wnd->VectorList;
    int i, coll = -1;
    for (i = 0; i < wnd->VectorCount; i++)    {
        if ((vc+i)->vt == VECTOR)    {
            rcc = (vc+i)->rc;
            /* --- skip the colliding vector --- */
            if (rcc.lf == rc.lf && rcc.rt == rc.rt &&
                    rcc.tp == rc.tp && rc.bt == rcc.bt)
                continue;
            if (rcc.tp == rcc.bt)    {
                /* ---- horizontal vector,
                    see if character is in it --- */
                if (rc.lf+x >= rcc.lf && rc.lf+x <= rcc.rt &&
                        rc.tp+y == rcc.tp)    {
                    /* --- it is --- */
                    if (rc.lf+x == rcc.lf)
                        coll = 0;
                    else if (rc.lf+x == rcc.rt)
                        coll = 2;
                    else
                        coll = 1;
                }
            }
            else     {
                /* ---- vertical vector,
                    see if character is in it --- */
                if (rc.tp+y >= rcc.tp && rc.tp+y <= rcc.bt &&
                        rc.lf+x == rcc.lf)    {
                    /* --- it is --- */
                    if (rc.tp+y == rcc.tp)
                        coll = 0;
                    else if (rc.tp+y == rcc.bt)
                        coll = 2;
                    else
                        coll = 1;
                }
            }
        }
    }
    return coll;
}

static void PaintVector(WINDOW wnd, RECT rc)
{
    int i, cw, fml, vertvect, coll, len;
    unsigned int ch, nc;

    if (rc.rt == rc.lf)    {
        /* ------ vertical vector ------- */
        nc = '3';
        vertvect = 1;
        len = rc.bt-rc.tp+1;
    }
    else     {
        /* ------ horizontal vector ------- */
        nc = 'D';
        vertvect = 0;
        len = rc.rt-rc.lf+1;
    }

    for (i = 0; i < len; i++)    {
        unsigned int newch = nc;
        int xi = 0, yi = 0;
        if (vertvect)
            yi = i;
        else
            xi = i;
        ch = videochar(GetClientLeft(wnd)+rc.lf+xi,
                    GetClientTop(wnd)+rc.tp+yi);
        for (cw = 0; cw < sizeof(CharInWnd); cw++)    {
            if (ch == CharInWnd[cw])    {
                /* ---- hit another vector character ---- */
                if ((coll=FindVector(wnd, rc, xi, yi)) != -1) {
                    /* compute first/middle/last subscript */
                    if (i == len-1)
                        fml = 2;
                    else if (i == 0)
                        fml = 0;
                    else
                        fml = 1;
                    newch = VectCvt[coll][cw][vertvect][fml];
                }
            }
        }
        PutWindowChar(wnd, newch, rc.lf+xi, rc.tp+yi);
    }
}

static void PaintBar(WINDOW wnd, RECT rc, enum VectTypes vt)
{
    int i, vertbar, len;
    unsigned int tys[] = {'[', '2', '1', '0'};
    unsigned int nc = tys[vt-1];

    if (rc.rt == rc.lf)    {
        /* ------ vertical bar ------- */
        vertbar = 1;
        len = rc.bt-rc.tp+1;
    }
    else     {
        /* ------ horizontal bar ------- */
        vertbar = 0;
        len = rc.rt-rc.lf+1;
    }

    for (i = 0; i < len; i++)    {
        int xi = 0, yi = 0;
        if (vertbar)
            yi = i;
        else
            xi = i;
        PutWindowChar(wnd, nc, rc.lf+xi, rc.tp+yi);
    }
}

static void PaintMsg(WINDOW wnd)
{
    int i;
    VECT *vc = wnd->VectorList;
    for (i = 0; i < wnd->VectorCount; i++)    {
        if (vc->vt == VECTOR)
            PaintVector(wnd, vc->rc);
        else
            PaintBar(wnd, vc->rc, vc->vt);
        vc++;
    }
}

static void DrawVectorMsg(WINDOW wnd,PARAM p1,enum VectTypes vt)
{
    if (p1)    {
        wnd->VectorList = realloc(wnd->VectorList,
                sizeof(VECT) * (wnd->VectorCount + 1));
        if (wnd->VectorList != NULL)    {
            VECT vc;
            vc.vt = vt;
            vc.rc = *(RECT *)p1;
            *(((VECT *)(wnd->VectorList))+wnd->VectorCount)=vc;
            wnd->VectorCount++;
        }
    }
}

static void DrawBoxMsg(WINDOW wnd, PARAM p1)
{
    if (p1)    {
        RECT rc = *(RECT *)p1;
        rc.bt = rc.tp;
        SendMessage(wnd, DRAWVECTOR, (PARAM) &rc, TRUE);
        rc = *(RECT *)p1;
        rc.lf = rc.rt;
        SendMessage(wnd, DRAWVECTOR, (PARAM) &rc, FALSE);
        rc = *(RECT *)p1;
        rc.tp = rc.bt;
        SendMessage(wnd, DRAWVECTOR, (PARAM) &rc, TRUE);
        rc = *(RECT *)p1;
        rc.rt = rc.lf;
        SendMessage(wnd, DRAWVECTOR, (PARAM) &rc, FALSE);
    }
}

int PictureProc(WINDOW wnd, MESSAGE msg, PARAM p1, PARAM p2)
{
    switch (msg)    {
        case PAINT:
            BaseWndProc(PICTUREBOX, wnd, msg, p1, p2);
            PaintMsg(wnd);
            return TRUE;
        case DRAWVECTOR:
            DrawVectorMsg(wnd, p1, VECTOR);
            return TRUE;
        case DRAWBOX:
            DrawBoxMsg(wnd, p1);
            return TRUE;
        case DRAWBAR:
            DrawVectorMsg(wnd, p1, p2);
            return TRUE;
        case CLOSE_WINDOW:
            if (wnd->VectorList != NULL)
                free(wnd->VectorList);
            break;
        default:
            break;
    }
    return BaseWndProc(PICTUREBOX, wnd, msg, p1, p2);
}

static RECT PictureRect(int x, int y, int len, int hv)
{
    RECT rc;
    rc.lf = rc.rt = x;
    rc.tp = rc.bt = y;
    if (hv)
        /* ---- horizontal vector ---- */
        rc.rt += len-1;
    else
        /* ---- vertical vector ---- */
        rc.bt += len-1;
    return rc;
}

void DrawVector(WINDOW wnd, int x, int y, int len, int hv)
{
    RECT rc = PictureRect(x,y,len,hv);
    SendMessage(wnd, DRAWVECTOR, (PARAM) &rc, 0);
}

void DrawBox(WINDOW wnd, int x, int y, int ht, int wd)
{
    RECT rc;
    rc.lf = x;
    rc.tp = y;
    rc.rt = x+wd-1;
    rc.bt = y+ht-1;
    SendMessage(wnd, DRAWBOX, (PARAM) &rc, 0);
}

void DrawBar(WINDOW wnd,enum VectTypes vt,int x,int y,int len,int hv)
{
    RECT rc = PictureRect(x,y,len,hv);
    SendMessage(wnd, DRAWBAR, (PARAM) &rc, (PARAM) vt);
}






[LISTING FOUR]


/* ------------- calendar.c ------------- */
#include "dflat.h"

#ifndef TURBOC

#define CALHEIGHT 17
#define CALWIDTH 33

static int DyMo[] = {31,28,31,30,31,30,31,31,30,31,30,31};
static struct tm ttm;
static int dys[42];
static WINDOW Cwnd;

static void FixDate(void)
{
    /* ---- adjust Feb for leap year ---- */
    DyMo[1] = (ttm.tm_year % 4) ? 28 : 29;
#ifndef BCPP
    /* bug in the Borland C++ mktime function prohibits this */
    ttm.tm_mday = min(ttm.tm_mday, DyMo[ttm.tm_mon]);
#endif
}

/* ---- build calendar dates array ---- */
static void BuildDateArray(void)
{
    int offset, dy = 0;
    memset(dys, 0, sizeof dys);
    FixDate();
    /* ----- compute the weekday for the 1st ----- */
    offset = ((ttm.tm_mday-1) - ttm.tm_wday) % 7;
    if (offset < 0)
        offset += 7;
    if (offset)
        offset = (offset - 7) * -1;
    /* ----- build the dates into the array ---- */
    for (dy = 1; dy <= DyMo[ttm.tm_mon]; dy++)
        dys[offset++] = dy;
}
static void CreateWindowMsg(WINDOW wnd)
{
    int x, y;
    DrawBox(wnd, 1, 2, CALHEIGHT-4, CALWIDTH-4);
    for (x = 5; x < CALWIDTH-4; x += 4)
        DrawVector(wnd, x, 2, CALHEIGHT-4, FALSE);
    for (y = 4; y < CALHEIGHT-3; y+=2)
        DrawVector(wnd, 1, y, CALWIDTH-4, TRUE);
}
static void DisplayDates(WINDOW wnd)
{
    int week, day;
    char dyln[10];
    int offset;
    char banner[CALWIDTH-1];
    char banner1[30];

    SetStandardColor(wnd);
    PutWindowLine(wnd, "Sun Mon Tue Wed Thu Fri Sat", 2, 1);
    memset(banner, ' ', CALWIDTH-2);
    strftime(banner1, 16, "%B, %Y", &ttm);
    offset = (CALWIDTH-2 - strlen(banner1)) / 2;
    strcpy(banner+offset, banner1);
    strcat(banner, "    ");
    PutWindowLine(wnd, banner, 0, 0);
    BuildDateArray();
    for (week = 0; week < 6; week++)    {
        for (day = 0; day < 7; day++)    {
            int dy = dys[week*7+day];
            if (dy == 0)
                strcpy(dyln, "   ");
            else    {
                if (dy == ttm.tm_mday)
                    sprintf(dyln, "%c%c%c%2d %c",
                        CHANGECOLOR,
                        SelectForeground(wnd)+0x80,
                        SelectBackground(wnd)+0x80,
                        dy, RESETCOLOR);
                else
                    sprintf(dyln, "%2d ", dy);
            }
            SetStandardColor(wnd);
            PutWindowLine(wnd, dyln, 2 + day * 4, 3 + week*2);
        }
    }
}
static int KeyboardMsg(WINDOW wnd, PARAM p1)
{
    switch ((int)p1)    {
        case PGUP:
            if (ttm.tm_mon == 0)    {
                ttm.tm_mon = 12;
                ttm.tm_year--;
            }
            ttm.tm_mon--;
            FixDate();
            mktime(&ttm);
            DisplayDates(wnd);
            return TRUE;
        case PGDN:
            ttm.tm_mon++;
            if (ttm.tm_mon == 12)    {
                ttm.tm_mon = 0;
                ttm.tm_year++;
            }
            FixDate();
            mktime(&ttm);
            DisplayDates(wnd);
            return TRUE;
        default:
            break;
    }
    return FALSE;
}
static int CalendarProc(WINDOW wnd,MESSAGE msg,PARAM p1,PARAM p2)
{
    switch (msg)    {
        case CREATE_WINDOW:
            DefaultWndProc(wnd, msg, p1, p2);
            CreateWindowMsg(wnd);
            return TRUE;
        case KEYBOARD:
            if (KeyboardMsg(wnd, p1))
                return TRUE;
            break;
        case PAINT:
            DefaultWndProc(wnd, msg, p1, p2);
            DisplayDates(wnd);
            return TRUE;
        case COMMAND:
            if ((int)p1 == ID_HELP)    {
                DisplayHelp(wnd, "Calendar");
                return TRUE;
            }
            break;
        case CLOSE_WINDOW:
            Cwnd = NULL;
            break;
        default:
            break;
    }
    return DefaultWndProc(wnd, msg, p1, p2);
}
void Calendar(WINDOW pwnd)
{
    if (Cwnd == NULL)    {
        time_t tim = time(NULL);
        ttm = *localtime(&tim);
        Cwnd = CreateWindow(PICTUREBOX,
                    "Calendar",
                    -1, -1, CALHEIGHT, CALWIDTH,
                    NULL, pwnd, CalendarProc,
                    SHADOW     |
                    MINMAXBOX  |
                    CONTROLBOX |
                    MOVEABLE   |
                    HASBORDER
        );
    }
    SendMessage(Cwnd, SETFOCUS, TRUE, 0);
}

#endif






[LISTING FIVE]


/* ------------ barchart.c ----------- */
#include "dflat.h"

#define BCHEIGHT 12
#define BCWIDTH 44
#define COLWIDTH 4

static WINDOW Bwnd;

/* ------- project schedule array ------- */
static struct ProjChart {
    char *prj;
    int start, stop;
} projs[] = {
    {"Center St", 0,3},
    {"City Hall", 0,5},
    {"Rt 395   ", 1,4},
    {"Sky Condo", 2,3},
    {"Out Hs   ", 0,4},
    {"Bk Palace", 1,5}
};

static char *Title =  "              PROJECT SCHEDULE";
static char *Months = "           Jan Feb Mar Apr May Jun";

static int BarChartProc(WINDOW wnd, MESSAGE msg,PARAM p1, PARAM p2)
{
    switch (msg)    {
        case COMMAND:
            if ((int)p1 == ID_HELP)    {
                DisplayHelp(wnd, "BarChart");
                return TRUE;
            }
            break;
        case CLOSE_WINDOW:
            Bwnd = NULL;
            break;
        default:
            break;
    }
    return DefaultWndProc(wnd, msg, p1, p2);
}
void BarChart(WINDOW pwnd)
{
    int pct = sizeof projs / sizeof(struct ProjChart);
    int i;

    if (Bwnd == NULL)    {
        Bwnd = CreateWindow(PICTUREBOX,
                    "BarChart",
                    -1, -1, BCHEIGHT, BCWIDTH,
                    NULL, pwnd, BarChartProc,
                    SHADOW     |
                    CONTROLBOX |
                    MOVEABLE   |
                    HASBORDER
        );
        SendMessage(Bwnd, ADDTEXT, (PARAM) Title, 0);
        SendMessage(Bwnd, ADDTEXT, (PARAM) "", 0);
        for (i = 0; i < pct; i++)    {
            SendMessage(Bwnd,ADDTEXT,(PARAM)projs[i].prj,0);
            DrawBar(Bwnd, SOLIDBAR+(i%4),
                11+projs[i].start*COLWIDTH, 2+i,
                (1 + projs[i].stop-projs[i].start) * COLWIDTH,
                TRUE);
        }
        SendMessage(Bwnd, ADDTEXT, (PARAM) "", 0);
        SendMessage(Bwnd, ADDTEXT, (PARAM) Months, 0);
        DrawBox(Bwnd, 10, 1, pct+2, 25);
    }
    SendMessage(Bwnd, SETFOCUS, TRUE, 0);
}

Copyright © 1992, Dr. Dobb's Journal