C PROGRAMMING

Comparing D-Flat and D-Flat++

This article contains the following executables: DFPP01.ARC

Al Stevens

The evolution of D-Flat++ continues to reveal some interesting surprises. The library has grown to where it now has enough classes to build a simple application with a menu and a few controls. The source code that implements these features is smaller and easier to read than its D-Flat counterparts in C. This month we look at the Application, Control, and TextBox window classes and compare the source code for these modules with the D-Flat C source code for the same features. My December 1991 column discussed D-Flat text boxes, and the May 1992 column discussed the application window. You will find the C source code for those modules in those issues.

The DF++ Application and TextBox classes derive from the DFWindow class, which we discussed last month. Every DF++ application has an Application window object, and most of the controls--menus, list boxes, edit boxes, buttons, and so on--derive from the TextBox class, so the Application and TextBox classes are, in effect, the foundation for the DF++ application. The Control class is the base for all control windows, and it encapsulates the behavior that control windows share.

The Application Class

Listing One, page 138, is applicat.h, the header file that describes the Application window class. A significant difference exists between D-Flat and D-Flat++ in the way that the two systems describe window classes, and applicat.h illustrates the difference by its relative simplicity. D-Flat has the data members for all window classes in a common window structure. The advantage is that the code for every class does not need to de-reference class-specific data-block extensions. The disadvantage is that every window class bears the size overhead of all classes. DF++ uses the C++ inheritance mechanism to derive window classes in a hierarchy. The result is simpler code and data structures that are no bigger than they need to be. The DF++ Application class has only three data members beyond the ones it inherits: a pointer to the MenuBar object, a pointer to the StatusBar object, and a switch that its Show message uses to indicate that the Application object is taking the focus.

Listing Two, page 138, is applicat.cpp. When you compare it to applicat.c from last May, the first thing you notice is that the C++ version is a lot smaller. There are several reasons for this. First, the C++ code does not need to intercept and interpret message codes in order to receive and process messages. DF++ messages are sent in the C++ tradition--by calls to class member functions. Therefore, each message is called directly by the sender and involves no message-passing logic like D-Flat uses. Second, applicat.cpp does not support the Window menu for multiple-document interface. Third, applicat.cpp does not support changing screen formats, shelling out to DOS, or the usual Help menu commands. Some applications will not use these features, and the ones that do will be better served by derived classes that include them.

The Application class's OpenWindow function registers the object with the DeskTop object by calling the SetApplication function, which allows the desktop, and therefore any other objects, to send messages to the application. The OpenWindow function declares the MenuBar and StatusBar objects. The MenuBar object is declared only if the Application constructor includes a pointer to an array of MenuBarItem objects, which define the menu bar and pop-down menus. We'll spend more time on menus in a later column.

The SetFocus and Show functions cause the Application window to take the focus differently than other windows. Most windows use the routine functions provided by the DFWindow base class. An Application window behaves differently. Rather than allow itself to be repainted whenever the user clicks on it, the Application window simply calls the Border function to let the window frame reflect the in-focus condition. This strategy prevents the system from repainting all the child windows every time the user clicks the Application window. The assumption is that they are already visible and that nothing in the Application window needs to be repainted just because the focus has shifted.

The Application window receives any keystrokes that the in-focus child window does not intercept. The DFWindow class sees to this by sending any unprocessed Keyboard messages to the parent of the window that received them. The Application window will use Ctrl+F4 and Alt+F4 to close the window. It sends all other Keyboard messages to the MenuBar object. This strategy allows the MenuBar to receive and process menu-bar shortcut keys and menu-command accelerator keys.

The Application window passes all ClockTick and StatusMessage messages to the StatusBar object to process.

The Control Class

Listings Three and Four, page 138, are control.h and control.cpp, respectively, the header files that define the Control class. All control window classes derive from this class. Its purpose is to define the behavior of a control window regardless of its function. Control windows are, by definition, user input devices that are child windows to another window. They may be children of an application-specific document window, a dialog box, or the Application window itself.

At present, the only behavior encapsulated by the Control class is the management of an enabled/disabled state variable and the processing of some default keystrokes. The Keyboard message intercepts the keys that move the focus between the sibling controls of a parent window. As I develop the controls, the dialog-box logic, and the help system, I'll no doubt add features to this class.

The D-Flat C library builds the generic control logic as a window-processing module in dialbox.c, which I described in my June 1992 column. It included code sensitive to the class type of the control window. The DF++ encapsulates that code into individual control-window classes.

The TextBox Class

Listing Five, page 138, is textbox.h, the header file that defines the TextBox class, which derives from the Control class. The TextBox is a base class for many other controls that will display, scroll, and page text. Edit boxes, list boxes, pop-down menus, pushbuttons, and others all derive directly or indirectly from the TextBox class, which encapsulates the behavior associated with scroll bars, text representation and display, and marked blocks of text. TextBox is a catch-all class. Almost every kind of window has text as its base.

A TextBox object consists of a window with a body of text and, optionally, horizontal and vertical scroll bars. The text is represented by a single String object named text, which contains newline characters to mark the ends of lines and a null character to mark the end of the text. The bufflen data member records the length of the string buffer, which is not always the same as the length of the string itself. The wlines data member is a line count and the TextPointers data member points to an array of integer text-line offsets. Each member of the array is an offset to the first character of a text line. This array supports efficient retrieval of specified lines of text. The textlen data member is the length of the text string, and the textwidth data member is the length of the longest line in the body of text. The wtop and wleft data members are offsets that represent the line and column positions of the text as it is currently displayed in the window. The BlkBeg... and BlkEnd... data members specify the start and end points of a marked block of text, if one exists.

Listing Six, page 139, is textbox.cpp, the code that implements the TextBox class. This module is representative of how control windows work. Most other controls will derive from the TextBox class, adding their own behavior. The TextBox class intercepts its Show message to add scroll bars to the window if there are none and the scroll-bar attributes are set. The TextBox class has unique messages of its own to initialize the text, append text, clear it, change the buffer length, and extract a specified line of text. There are internal messages, not available to users of the class, that display a line of text with attention given to embedded shortcut characters. These functions support the display of menu and botton labels, which have shortcut keys that must be displayed in a highlighting color.

The TextBox's Paint message paints the window by displaying the lines in the window's field of view. The Keyboard message intercepts the paging and scrolling keys to send paging and scrolling messages to the window, which in turn page and scroll the window horizontally and vertically. The TextBox class does not need to process mouse messages to page and scroll. Those messages are received by the child scroll bars, which are window classes of their own. They interpret the mouse messages and send paging and scrolling messages to the TextBox window object. This approach is in improvement over the way D-Flat manages paging and scrolling. The D-Flat text-box window intercepts and processes the scroll-bar mouse messages itself, a procedure that binds scroll bars to text boxes and does not permit them to be used for other purposes. This is an example of how C++ lures you into building better code. By allowing encapsulation, the language encourages you to organize th functions of an object into a separate definition and to bind that object as loosely as possible with other objects. You could always do that in C, and we always tried, but the lure wasn't there.

Messages and Virtual Functions

The D-Flat C library resembles the Windows API in the way that it manages messages. Any message can go to any window. If the window is not interested in the message, it can pass the message on to its base class, which has the same opportunity, and this proceeds all the way down to the base class. The window can process the message and/or pass it on as well. The window can reject the message by not allowing it to proceed, or supersede the processing by any base class by processing it and then not passing it on. These procedures are managed by the custom window-processing modules assigned to an instance of a window class and/or by the default window processing modules assigned to the class in its definition. All this looks very much like the object-oriented messages and polymorphism of C++, and it is similar, except that the program itself makes the polymorphic decisions at run time instead of having them defined in the class hierarchy. The main difference is that the C library's SendMessage process can send any message to any window through its window pointer, and the message will arrive, whether or not it gets processed. D-Flat++ has no SendMessage function. Messages are member functions. How is this different?

The DFWindow class has a number of virtual member functions that represent the lowest common denominator of window processing. All window classes have these messages in common. For example, any window might have a title, receive a keystroke, or be moved. If a window works fine with the default DFWindow behavior for those messages, it needs nothing more in its definition. If the window wants to modify that behavior, it provides its own overloaded member functions, and the behavior is appropriately modified.

Messages specific to a particular derived class are not defined in the DFWindow class. To illustrate, the PageUp message for the TextBox class is not defined in DFWindow. What are the implications of that? First, you cannot send the PageUp message to a window not derived from the TextBox class. Not that you'd want to, but even if you did, you cannot do it. Second, you cannot send the PageUp message to a TextBox window through a reference or pointer to one of its base classes. There might be times when you'd want to do that, but you cannot.

To fully emulate the message-passing paradigm of D-Flat or Windows, a C++ class library would need to include virtual member functions for every possible message in the topmost base window base class, which is DFWindow in D-Flat++. What would be wrong with that? Two things. First, every time you built a new window class to support your application, you'd have to add empty virtual member functions to the DFWindow class. Second, every window class would carry the overhead of every message. Why is that?

When you put virtual functions in a class, you add overhead. Each object of the class contains a vptr pointer to a vtbl table of virtual function pointers. If the virtual function is an empty function, code space is set aside for a function return statement. There are vptrs for the object class and each of its base classes. All objects of the same class point to a common vtbl. Windows has about 130 messages. D-Flat has about 75. If a class library encapsulated all those messages as empty virtual functions, there would be tables for every class of window object that a program declared. D-Flat has over 20 window classes. Windows has a lot more. If D-Flat++ had empty virtual functions for every message, there would be over 1500 vtbl entries plus the ones that would result as you added window classes to support your application.

Various C++ class libraries that wrap around the Windows API solve the problem in different ways. Borland's ObjectWindows Library extends the syntax of the C++ language to define message functions. The Microsoft Foundation Class library uses preprocessor macros to map message functions to a message map. The Windows++ class library that I discussed in December 1992, builds virtual functions for a few of the most commonly used messages and passes the others through a default function that the window class must interpret to its own purposes. D-Flat++ puts message functions in the class definitions at the highest level in the hierarchy where the message is relevant and assumes that the system and application will send messages only to objects of the window classes that expect them.

"You Can't Go Home Again"

Thomas Wolfe was right. Looking at D-Flat from the perspective of the C++ programmer, and having now solved the same problem in both languages, I can see many ways that the C code could have been better--more object oriented, perhaps, and certainly better organized. It makes me want to rewrite the C version, but that would be looking backward, and we don't want to do that.

RTFM: (Read the Friendly Manual--or README File)

About a year ago I reported bug to Borland, which I thought they ignored. I was a Windows 3.1 beta tester, and when I ran the Brief programmer's editor followed by the Borland C++ 3.0 command-line MAKE utility program, my computer crashed. I tested it on several computers and got the same result. The culprit turned out to be the Windows 3.1 EMM386.EXE memory manager. By using the DOS version of the memory manager I was able to circumvent the problem. Since Windows 3.1 was in beta and the bug involved a Solution Systems product (Brief 3.1) and a Borland product, it looked like nobody bothered to fix it.

In the meantime, Borland upgraded their C++ to 3.1 and acquired the Brief editor, and Windows 3.1 was released. The bug persisted. I still used the workaround, but there came a new wrinkle. The problem returned with the beta DOS 6.0's EMM386.EXE. This is a real concern because there are compelling reasons to use the newer EMM386.EXE. It frees up more conventional memory than the older one, and who knows what else in the new DOS depends on a more-current memory manager to work properly. I got online to the Borland sysops on CompuServe and asked if anyone had any comments.

If you are one of the many programmers who uses DOS, Brief, and the BC++ command-line programs, and if you expect to use Windows or upgrade to DOS 6.0, you need to know about this one. The sysops pointed me to a neglected (by me) item tucked away in the BC++ 3.1 README file that says to install a patch in their DPMI software to get around a condition related to the Windows 3.1 (and now the DOS 6.0) memory manager. I'm not sure why it's a patch and not a permanent part of the software, but I made it, and now everything works fine. Ironically, the discussion of the patch comes just before, but not within, a section titled, "IMPORTANT INFORMATION."



_C PROGRAMMING COLUMN_
by Al Stevens


[LISTING ONE]


// -------------- applicat.h
#ifndef APPLICAT_H
#define APPLICAT_H
#include "menubar.h"
#include "statbar.h"
class Application : public DFWindow    {
    MenuBar *menubar;            // points to menu bar
    StatusBar *statusbar;        // points to status bar
    Bool takingfocus;            // true while taking focus
    virtual void SetColors();
protected:
    // ------------- client window coordinate adjustments
    virtual void AdjustBorders();
public:
    Application(char *ttl,int lf,int tp,int ht,int wd,MenuBarItem *Menu = NULL)
                : DFWindow(ttl, lf, tp, ht, wd, NULL)
            { OpenWindow(Menu); }
    Application(char *ttl, int ht, int wd, MenuBarItem *Menu = NULL)
                : DFWindow(ttl, ht, wd, NULL)
            { OpenWindow(Menu); }
    Application(int lf, int tp, int ht, int wd, MenuBarItem *Menu = NULL)
                : DFWindow(lf, tp, ht, wd, NULL)
            { OpenWindow(Menu); }
    Application(int ht, int wd, MenuBarItem *Menu = NULL)
                : DFWindow(ht, wd, NULL)
            { OpenWindow(Menu); }
    Application(char *ttl, MenuBarItem *Menu = NULL)
                : DFWindow(ttl)
            { OpenWindow(Menu); }
    virtual ~Application()
            { if (windowstate != CLOSED) CloseWindow(); }
    // -------- API messages
    virtual void OpenWindow() { OpenWindow(NULL); }
    void OpenWindow(MenuBarItem *menu);
    virtual void CloseWindow();
    virtual Bool SetFocus();
    virtual void Show();
    virtual void Keyboard(int key);
    virtual void ClockTick();
    void StatusMessage(String& Msg);
};
void DispatchEvents(Application *ApWnd);
void InitializeEvents(void);
#endif






[LISTING TWO]


// ------------ applicat.cpp
#include "dflatpp.h"
void Application::OpenWindow(MenuBarItem *menu)
{
    extern DeskTop desktop;
    desktop.SetApplication(this);
    windowtype = ApplicationWindow;
    if (windowstate == CLOSED)
        DFWindow::OpenWindow();
    SetAttribute(BORDER | SAVESELF | CONTROLBOX | STATUSBAR);
    SetColors();
    if (menu != NULL)       {
        SetAttribute(MENUBAR);
        menubar = new MenuBar(menu, this);
    }
    else
        menubar = NULL;
    statusbar = new StatusBar(this);
    desktop.mouse().Show();
    DFWindow::SetFocus();
    takingfocus = False;
}
void Application::CloseWindow()
{
    if (menubar != NULL)
        delete menubar;
    if (statusbar != NULL)
        delete statusbar;
    desktop.SetApplication(NULL);
    DFWindow::CloseWindow();
}
// -------- set the fg/bg colors for the window
void Application::SetColors()
{
    colors.fg =
    colors.sfg =
    colors.ffg =
    colors.hfg = LIGHTGRAY;
    colors.bg =
    colors.sbg =
    colors.fbg =
    colors.hbg = BLUE;
}
void Application::AdjustBorders()
{
    DFWindow::AdjustBorders();
    if (attrib & MENUBAR)
        TopBorderAdj++;
    if (attrib & STATUSBAR)
        BottomBorderAdj = 1;
}
Bool Application::SetFocus()
{
    takingfocus = True;
    DFWindow::SetFocus();
    takingfocus = False;
    return True;
}
void Application::Show()
{
    if (!takingfocus || !isVisible())
        DFWindow::Show();
    else
        Border();
}
void Application::Keyboard(int key)
{
    switch (key)    {
        case CTRL_F4:
        case ALT_F4:
            CloseWindow();
            break;
        default:
            // ---- forward unprocessed keys to the menubar
            if (menubar != NULL)
                menubar->Keyboard(key);
            break;
    }
}
void Application::ClockTick()
{
    if (statusbar != NULL)
        statusbar->ClockTick();
}
void Application::StatusMessage(String& Msg)
{
    if (statusbar != NULL)
        statusbar->StatusMessage(Msg);
}







[LISTING THREE]


// ---------- control.h
#ifndef CONTROL_H
#define CONTROL_H
#include "dfwindow.h"
class TextBox;
class Control : public DFWindow    {
    Bool enabled;       // true if control is enabled
public:
    Control(char *ttl,int lf,int tp,int ht,int wd,DFWindow *par)
                        : DFWindow(ttl, lf, tp, ht, wd, par)
        { OpenControl(); }
    Control(char *ttl, int ht, int wd, DFWindow *par)
                        : DFWindow(ttl, ht, wd, par)
        { OpenControl(); }
    Control(int lf, int tp, int ht, int wd, DFWindow *par)
                        : DFWindow(lf, tp, ht, wd, par)
        { OpenControl(); }
    Control(int ht,int wd,DFWindow *par) : DFWindow(ht,wd,par)
        { OpenControl(); }
    Control(char *ttl)    : DFWindow(ttl)
        { OpenControl(); }
    virtual ~Control() { /* null */ }
    virtual void Keyboard(int key);
    void OpenControl() { Enable(); }
    void Enable()      { enabled = True; }
    void Disable()     { enabled = False; }
    Bool isEnabled()   { return enabled; }
};
#endif







[LISTING FOUR]


// ------------ control.cpp
#include "control.h"
#include "desktop.h"
void Control::Keyboard(int key)
{
    DFWindow *Wnd;
    switch (key)    {
        case UP:
            Wnd = desktop.InFocus();
            do {
                PrevSiblingFocus();
                if (Wnd == desktop.InFocus())
                    break;
            } while (desktop.InFocus()->WindowType() == MenubarWindow);
            break;
        case '\t':
        case DN:
        case ALT_F6:
            Wnd = desktop.InFocus();
            do {
                NextSiblingFocus();
                if (Wnd == desktop.InFocus())
                    break;
            } while (desktop.InFocus()->WindowType() ==
                    MenubarWindow);
            break;
        default:
            DFWindow::Keyboard(key);
            break;
    }
}







[LISTING FIVE]


// ------------- textbox.h
#ifndef TEXTBOX_H
#define TEXTBOX_H
#include "control.h"
const int SHORTCUTCHAR = '~';
const int InitialBufferSize = 1024;
class ScrollBar;
class TextBox : public Control    {
    ScrollBar *hscrollbar;  // horizontal scroll bar
    ScrollBar *vscrollbar;  // vertical scroll bar
    unsigned *TextPointers; // -> list of line offsets
    Bool resetscrollbox;
protected:
    // ---- text buffer
    String *text;           // window text
    unsigned int bufflen;   // length of buffer
    int wlines;             // number of lines of text
    unsigned int textlen;   // text length
    int textwidth;          // width of longest line in textbox
    // ---- text display
    int wtop;               // text line on top of display
    int wleft;              // left position in window viewport
    int BlkBegLine;         // beginning line of marked block
    int BlkBegCol;          // beginning column of marked block
    int BlkEndLine;         // ending line of marked block
    int BlkEndCol;          // ending column of marked block
    int shortcutfg;         // shortcut key color
    char *TextLine(int line)
        { return (char *)(*text) + *(TextPointers+line); }
    int DisplayShortcutField(
        String sc, int x, int y, int fg, int bg);
    void WriteShortcutLine(int lno, int fg, int bg);
    void WriteTextLine(int lno, int fg, int bg);
    void BuildTextPointers();
    void SetScrollBoxes();
    virtual void SetColors();
public:
    TextBox(char *ttl,int lf,int tp,int ht,int wd,DFWindow *par)
                        : Control(ttl, lf, tp, ht, wd, par)
            { OpenWindow(); }
    TextBox(char *ttl, int ht, int wd, DFWindow *par)
                        : Control(ttl, ht, wd, par)
            { OpenWindow(); }
    TextBox(int lf, int tp, int ht, int wd, DFWindow *par)
                        : Control(lf, tp, ht, wd, par)
            { OpenWindow(); }
    TextBox(int ht, int wd, DFWindow *par) : Control(ht,wd,par)
            { OpenWindow(); }
    TextBox(char *ttl)    : Control(ttl)
            { OpenWindow(); }
    virtual ~TextBox()
        { if (windowstate != CLOSED) CloseWindow(); }
    // -------- textbox API messages
    virtual void ScrollUp();
    virtual void ScrollDown();
    virtual void ScrollRight();
    virtual void ScrollLeft();
    virtual void PageUp();
    virtual void PageDown();
    virtual void PageRight();
    virtual void PageLeft();
    virtual void Home();
    virtual void End();
    virtual void OpenWindow();
    virtual void CloseWindow();
    virtual void AddText(char *txt);
    virtual void SetText(char *txt);
    virtual void SetTextLength(unsigned int len);
    virtual void ClearText();
    virtual void Show();
    virtual void Paint();
    virtual void Keyboard(int key);
    String ExtractTextLine(int lno);
    void ClearTextBlock()
        { BlkBegLine=BlkEndLine=BlkBegCol=BlkEndCol=0; }
    void HorizontalPagePosition(int pct);
    void VerticalPagePosition(int pct);
};
#endif





[LISTING SIX]


// ------------- textbox.cpp
#include "desktop.h"
#include "textbox.h"
#include "scrolbar.h"
// ----------- common constructor code
void TextBox::OpenWindow()
{
    windowtype = TextboxWindow;
    if (windowstate == CLOSED)
        Control::OpenWindow();
    text = NULL;
    bufflen = InitialBufferSize;
    hscrollbar = vscrollbar = NULL;
    TextPointers = NULL;
    ClearText();
    SetColors();
}
void TextBox::CloseWindow()
{
    ClearText();
    if (hscrollbar != NULL)
        delete hscrollbar;
    if (vscrollbar != NULL)
        delete vscrollbar;
    Control::CloseWindow();
}
// ------ show the textbox
void TextBox::Show()
{
    if ((attrib & HSCROLLBAR) && hscrollbar == NULL)    {
        hscrollbar = new ScrollBar(HORIZONTAL, this);
        hscrollbar->SetAttribute(FRAMEWND);
    }
    if ((attrib & VSCROLLBAR) && vscrollbar == NULL)    {
        vscrollbar = new ScrollBar(VERTICAL, this);
        vscrollbar->SetAttribute(FRAMEWND);
    }
    Control::Show();
}
// ------------ build the text line pointers
void TextBox::BuildTextPointers()
{
    textwidth = wlines = 0;
    // ---- count the lines of text
    char *cp1, *cp = *text;
    while (*cp)    {
        wlines++;
        while (*cp && *cp != '\n')
            cp++;
        if (*cp)
            cp++;
    }
    // ----- build the pointer array
    delete TextPointers;
    TextPointers = new unsigned int[wlines];
    unsigned int off;
    cp = *text;
    wlines = 0;
    while (*cp)    {
        off = (unsigned int) (cp - *text);
        *(TextPointers + wlines++) = off;
        cp1 = cp;
        while (*cp && *cp != '\n')
            cp++;
        textwidth = max(textwidth, (unsigned int) (cp - cp1));
        if (*cp)
            cp++;
    }
}
// --------- add a line of text to the textbox
void TextBox::AddText(char *txt)
{
    String tx(txt);
    int len = tx.Strlen();
    if (tx[len-1] != '\n')
        textlen++;
    textlen += len;
    if (text == NULL)
        text = new String(bufflen);
    if (textlen > text->StrBufLen())
        text->ChangeLength(max(bufflen, textlen));
    *text += tx;
    if (*text[len-1] != '\n')
        *text += String("\n");
    BuildTextPointers();
}
// --------- set the textbox's text buffer to new text
void TextBox::SetText(char *txt)
{
    ClearText();
    AddText(txt);
}
// ------ set the length of the text buffer
void TextBox::SetTextLength(unsigned int len)
{
    if (text != NULL)
        text->ChangeLength(len);
    bufflen = len;
}
// --------- clear the text from the textbox
void TextBox::ClearText()
{
    if (text != NULL)
        delete text;
    textlen = 0;
    wlines = 0;
    textwidth = 0;
    wtop = wleft = 0;
    ClearTextBlock();
    if (TextPointers != NULL)
        delete TextPointers;
}
// -------- set the fg/bg colors for the window
void TextBox::SetColors()
{
    colors.fg = BLACK;
    colors.bg = LIGHTGRAY;
    colors.sfg = LIGHTGRAY;
    colors.sbg = BLACK;
    colors.ffg = BLACK;
    colors.fbg = LIGHTGRAY;
    colors.hfg = BLACK;
    colors.hbg = LIGHTGRAY;
    shortcutfg = BLUE;
}
// ------- extract a text line
String TextBox::ExtractTextLine(int lno)
{
    char *lp = TextLine(lno);
    int offset = lp - (char *) *text;
    for (int len = 0; *(lp+len) && *(lp+len) != '\n'; len++)
        ;
    return text->mid(len, offset);
}
// ---- display a line with a shortcut key character
void TextBox::WriteShortcutLine(int lno, int fg, int bg)
{
    String sc = ExtractTextLine(lno);
    int x = sc.Strlen();
    int y = lno-wtop;
    x -= DisplayShortcutField(sc, 0, y, fg, bg);
    // --------- pad the line
    int wd = ClientWidth() - x;
    if (wd > 0)
        WriteClientString(String(wd, ' '), x, y, fg, bg);
}
// ---- display a shortcut field character
int TextBox::DisplayShortcutField(String sc, int x, int y, int fg, int bg)
{
    int scs = 0;
    int off = sc.FindChar(SHORTCUTCHAR);
    if (off != -1)    {
        scs++;
        if (off != 0)    {
            String ls = sc.left(off);
            WriteClientString(ls, x, y, fg, bg);
        }
        WriteClientChar(sc[off+1], x+off, y, shortcutfg, bg);
        int len = sc.Strlen()-off-2;
        if (len > 0)    {
            String rs = sc.right(len);
            scs += DisplayShortcutField(rs, x+off+1, y, fg, bg);
        }
    }
    else
        WriteClientString(sc, x, y, fg, bg);
    return scs;
}
// ------- write a text line to the textbox
void TextBox::WriteTextLine(int lno, int fg, int bg)
{
    if (lno < wtop || lno >= wtop + ClientHeight())
        return;
    int wd = ClientWidth();
    String tl = ExtractTextLine(lno);
    String ln = tl.mid(wd, wleft);
    int dif = wd-ln.Strlen();
    if (dif > 0)
        ln = ln + String(dif, ' ');    // pad the line with spaces
    // ----- display the line
    WriteClientString(ln, 0, lno-wtop, fg, bg);
}
// ---------- paint the textbox
void TextBox::Paint()
{
    if (text == NULL)
        Control::Paint();
    else    {
        int ht = ClientHeight();
        int wd = ClientWidth();
        int fg = colors.fg;
        int bg = colors.bg;
        for (int i = 0; i < min(wlines-wtop,ht); i++)
            WriteTextLine(wtop+i, fg, bg);
        // ---- pad the bottom lines in the window
        String line(wd, ' ');
        while (i < ht)
            WriteClientString(line, 0, i++, fg, bg);
        if (resetscrollbox)
            SetScrollBoxes();
        resetscrollbox = False;
    }
}
// ------ process a textbox keystroke
void TextBox::Keyboard(int key)
{
    switch (key)    {
        case UP:
            if (ClientTop() == ClientBottom())
                break;
            ScrollDown();
            return;
        case DN:
            if (ClientTop() == ClientBottom())
                break;
            ScrollUp();
            return;
        case FWD:
            ScrollLeft();
            return;
        case BS:
            ScrollRight();
            return;
        case PGUP:
            PageUp();
            return;
        case PGDN:
            PageDown();
            return;
        case CTRL_PGUP:
            PageLeft();
            return;
        case CTRL_PGDN:
            PageRight();
            return;
        case HOME:
            Home();
            return;
        case END:
            End();
            return;
        default:
            break;
    }
    Control::Keyboard(key);
}
// ------- scroll up one line
void TextBox::ScrollUp()
{
    if (wtop < wlines-1)    {
        int fg = colors.fg;
        int bg = colors.bg;
        desktop.screen().Scroll(ClientRect(), 1, fg, bg);
        wtop++;
        int ln = wtop+ClientHeight()-1;
        if (ln < wlines)
            WriteTextLine(ln, fg, bg);
        SetScrollBoxes();
    }
}
// ------- scroll down one line
void TextBox::ScrollDown()
{
    if (wtop)    {
        int fg = colors.fg;
        int bg = colors.bg;
        desktop.screen().Scroll(ClientRect(), 0, fg, bg);
        --wtop;
        WriteTextLine(wtop, fg, bg);
        SetScrollBoxes();
    }
}
// ------- scroll left one character
void TextBox::ScrollLeft()
{
    if (wleft < textwidth-1)
        wleft++;
    Paint();
}
// ------- scroll right one character
void TextBox::ScrollRight()
{
    if (wleft > 0)
        --wleft;
    Paint();
}
// ------- page up one screenfull
void TextBox::PageUp()
{
    if (wtop)    {
        wtop -= ClientHeight();
        if (wtop < 0)
            wtop = 0;
        resetscrollbox = True;
        Paint();
    }
}
// ------- page down one screenfull
void TextBox::PageDown()
{
    if (wtop < wlines-1)    {
        wtop += ClientHeight();
        if (wlines < wtop)
            wtop = wlines-1;
        resetscrollbox = True;
        Paint();
    }
}
// ------- page right one screenwidth
void TextBox::PageRight()
{
    if (wleft < textwidth-1)    {
        wleft += ClientWidth();
        if (textwidth < wleft)
            wleft = textwidth-1;
        resetscrollbox = True;
        Paint();
    }
}
// ------- page left one screenwidth
void TextBox::PageLeft()
{
    if (wleft)    {
        wleft -= ClientWidth();
        if (wleft < 0)
            wleft = 0;
        resetscrollbox = True;
        Paint();
    }
}
// ----- move to the first line of the textbox
void TextBox::Home()
{
    wtop = 0;
    Paint();
}
// ----- move to the last line of the textbox
void TextBox::End()
{
    wtop = wlines-ClientHeight();
    if (wtop < 0)
        wtop = 0;
    Paint();
}
// ----- position the scroll boxes
void TextBox::SetScrollBoxes()
{
    if (vscrollbar != NULL)
        vscrollbar->TextPosition(wlines ? (wtop*100)/wlines : 0);
    if (hscrollbar != NULL)
        hscrollbar->TextPosition(textwidth ? (wleft*100)/textwidth : 0);
}
// ---- compute the horizontal page position
void TextBox::HorizontalPagePosition(int pct)
{
    wleft = (textwidth * pct) / 100;
    Paint();
}
// ---- compute the vertical page position
void TextBox::VerticalPagePosition(int pct)
{
    wtop = (wlines * pct) / 100;
    Paint();
}






Copyright © 1993, Dr. Dobb's Journal