C Programming

Al continues with D-Flat++, this month implementing classes for tool bars and tool buttons using a text-editor named TED to illustrate how you can use these classes. TED sports a menu bar, status bar, and tool bar, and it lets you work on one text file at a time.


December 01, 1993
URL:http://www.drdobbs.com/cpp/c-programming/184409131

DEC93: C PROGRAMMING

In The Origin of Species, Darwin presents his theory of evolution, which postulates that we and the other primates are derived classes with a common base. Darwin acknowledges the problems with his theory and tries to deal with each of them. Why, he asks, if we evolved from the lower species through a gradual process of natural selection, are there not large numbers of incremental species walking around the earth, each one representing one tiny advance in the evolutionary chain? His guess is that each new superior species eliminated its inferior ancestors by virtue of the survival of the fittest. Why, then, have no paleantological records of those interim species been found? His answer to that question is that the records themselves are incomplete, having been mostly erased by natural disasters and phenomena and that we are lucky to have uncovered the few traces that do remain.

The problem as Darwin saw it is not that there's a "missing link" but that there is a missing chain, with each link being only slightly different from and improved over the one before it.

We have tools today that Darwin could not have imagined. We have on our desktops technology advanced enough to build a computer model of this missing chain. The processed called "morphing," which DDJ covered in the July, 1993 issue, is the key. If we start with pictures of an ape and a human and morph from one to the other, we should have a sequence of image frames that would constitute, by computer simulation, the missing evolutionary chain. In the dead center of the sequence just might be that elusive missing link. A century and a half of scientific mystery and speculation would be ended.

I put it to the test, and it worked. I morphed an image of an ape into an image of a contemporary male human. Then, with hands trembling and to test my theory, I retrieved the image that occurred midway in the morphing sequence. The image that sprang to my screen, the missing link in the evolutionary chain, the answer to a question that has stumped the best minds of science for decades, is a dead ringer for The Kramer.

I'm working on my Nobel acceptance speech in my spare time.

D-Flat++: ToolBars and TED

I liked the way I implemented dialog boxes in D-Flat++. You derive a class from the DialogBox class and embed control objects in it. That's all it takes. The reason that Windows developers needed resource compilers was that the code to implement a dialog box or a menu does not resemble the output, structurally or any other way. It involved a bunch of function calls. The tabular format of the resource-compiler languages was an improvement because it gave mnemonic representation to the design. D-Flat++ substitutes a class design for a resource language, and loses none of the notational convenience in the process. It worked so well, that I used the same approach for the ToolBar and ToolButton classes. You derive your tool bar class from the base ToolBar class and embed ToolButton objects in it. Then you embed an object of the derived tool bar class in your derived application class. That worked so well, that I went back to the way that the menu bar is implemented and changed it to use the same approach.

It doesn't matter how well something is working, we can always find a reason to fix it.

This month I'll show you the new example application that demonstrates the D-Flat++ class library. It's a simple text editor called TED. It has a menu bar, a status bar, and a tool bar, and it lets you work on one text file at a time. After discussing TED, I'll look at how the tool bar and buttons work.

TED Version 1

Listing One, page 134, is ted.h, the source file that defines the Ted application class. Version 1 is a simple application. It has a menu bar and a tool bar. The application class embeds an EditBox class control object to handle the text editing. The class intercepts the Size message to change the size of the edit box whenever the user changes the size of the application window. There's a member function to build the application window's title to reflect the name of the file that the user is editing. Another member function prompts the user to save the file when changes have been made and the program is going to exit or use the window for a different file. The application class intercepts the CloseWindow message to make that test. There are a constructor and a destructor, and there is a member function for each of the commands on the menu.

Listing Two, page 134, is ted.cpp, which contains the member functions for the Ted class. There's little in the way of a text editor here. The EditBox class takes care of all that.

The menus for TED are built in the tedmenu.cpp source file, Listing Three, page 134. First are declarations of all the menu commands, which are instances of the MenuSelection class. I discussed that class and the others used in this source file in my April 1993 column. I'm including it here to show you what the TED menus look like. There are File, Edit, and Options menus. The File menu has New, Open, Save, Save As, and Exit commands. The Edit menu has Cut, Copy, and Paste. The Options menu has Insert and Word Wrap. Each of these commands, except for Word Wrap, has an associated member function in the derived Ted application class. Word Wrap is a toggle.

The TED toolbar is built in two files, tedtools.h, Listing Four, page 134, and tedtools.cpp, Listing Five, page 135. tedtools.h defines the tool bar, which is a class derived from the ToolBar class. It has three tool buttons to correspond to the New, Open, and Save menu commands. CUA programs typically use tool bars as mouse shortcuts to the commands available on menus. An application defines a tool bar by deriving a class from the ToolBar base class, embedding some ToolButton objects in the class and providing a constructor to initialize the buttons with labels and functions to call when they are pressed. Listing Five contains the constructor, which initializes the three ToolButton objects and calls their SetButtonFunction method to assign functions to them.

Designer Classes

The design of a complex class library from the ground up tends to topple conventional wisdom. Just when you think that you have a mature, working class, one whose details you can tuck away and forget about in true black-box engineering style, it comes back to haunt you. Why? Because you try to derive a new class from it, that's why. Nothing humbles a class designer more than the realization that some old and trusted class can't live up to its promise because it doesn't host a particular newly derived class with grace and hospitality. The notion that C++ and object-oriented design promote reusable software any more than traditional procedural design is a mistaken one. C++ encourages such design, to be sure, because the class structure holds the key to encapsulation. But designs are made to be modified, and the notion that you can do it once, top down, bottom up, upside down, or any other way, and chisel the details in stone never to be budged again is a flawed one.

Case in point: I have this handy PushButton class in D-Flat++. It derives from a base Button class that handles all the details common to all control buttons--whether they're enabled, current setting, when it gets pushed, and so on. The PushButton class implements the command button. When the user pushes it--releases it, actually--the class calls a function in the program's application class. The PushButton class has all the stuff that it needs to watch the keyboard and the mouse while the user holds the key or button down and moves it around--everything that a well-behaved push button has to do.

Buttons on a tool bar work just like push buttons except that they have a different look and are grouped on the tool bar instead of coexisting with other unrelated push buttons on a dialog box. Just perfect for inheritance. Most of the details are the same, and only two areas need to be customized. So I did it; I derived the ToolButton class from the PushButton class. And it works now. The trouble was, however, that there were a lot of details in the PushButton's member functions that should be inherited and some others that should be customized. What's that, you say? Why is that a problem? Isn't that exactly what polymorphism is all about? Yep. Except that those reasonable and replaceable implementation details were usually tucked together in the same PushButton member functions. You can't polymorphize part of a function and inherit the rest. Its all one way or the other. The pure object-oriented designer would have gritted the old choppers and overridden all those virtual functions. Not me. I know all about those details in that base class. I designed and built it. If they were coded differently--better--it would be easier to inherit their good stuff and ignore the parts that I don't need in the new class. So I just waded in and changed it.

That's a difficult problem to avoid because you can't always know in advance which classes are going to become base classes and what parts of them will be inherited and overridden. I haven't yet figured a way to look at class during design and spot all the functionally independent implementation details. I think that it takes a while to develop that eye, and I haven't seen any class designs yet that evince that kind of designer foresight.

Implementing the Tool Bar

Off the soapbox. Listing Six, page 135, is toolbar.h, the source file that defines the ToolBar and ToolButton classes. A tool bar is a simple thing. It is no more than a blank spot on the screen that stretches the length of the application window and that is the parent of the tool buttons.

When you are dealing with a 25x80 text-mode screen, there is not a lot of space to waste. These tool buttons are three character positions high to allow for frames and a label. The tool bar will be the same height. It is necessary to provide visual separation between the menu bar above and the document window below. There aren't enough character rows to let all these window pieces have their own frames, so the implementation uses color to separate them.

Listing Seven, page 135, is toolbar.cpp, the code that implements the ToolBar class. All that it does is position itself in the right place on the application window, and see that its size changes appropriately when the application window size changes.

Ted's Tools

Listing Eight, page 135, is toolbutt.cpp. (Don't laugh, they only give you eight characters for a filename.) The ToolButton class member functions are in this file. To visually differentiate these buttons from other controls, I implemented them as small windows with frames. I use the single-line frame characters for the top and left sides of the frame and the double-line characters for the right and bottom sides. That configuration gives the button a 3-D look. When the user clicks on the button, the program reverses the single and double lines, which makes it look like the button recesses when it is pushed.

There are three color configurations for a tool button, a normal one, a color set for while the button is pressed, and one for when the button is disabled. The three Color objects at the top of toolbutt.cpp define these colors. The two BoxLines structures define the frame characters for the two configurations. There are two constructors, one to make the button automatically position itself on the tool bar and one to assign a screen location for the button. The class intercepts the Border method to draw one frame or another, depending on whether the button is being pressed. The Paint interception assigns the correct color configuration depending on the button's current state. The SetFocus and ButtonCommand interceptions see to it that whichever window had the focus before the button was pressed gets it back after the button's command function returns. Other than that, the code lets the Button and PushButton base classes do most of the work.

How to Get the Source Code

D-Flat++ is still preliminary but far closer to a working version than the first two. I am writing this column in September and will soon upload version 3. The first version was incomplete, but there was enough of an implementation to give you an idea of how DF++ works and how it differs from D-Flat. The second version had enough functionality to build an application, and the third one improves on that although there are more features to come. Later versions will be released by the time you read this. The C D-Flat function library is still available, too. You can download DF and DF++ from the CompuServe DDJ Forum or from M&T Online. You can also get them by sending a stamped, self-addressed diskette mailer and a formatted diskette to me at Dr. Dobb's Journal, 411 Borel Avenue, San Mateo, CA 94402. The software is free, but if you wish, include a dollar for my Careware charity, the Brevard County Food Bank.

Hooked on Templates

In August, I attended Miller Freeman's East Coast edition of Software Development '93 in Boston. The show grows every year. I conducted a workshop on C++ object-database management based on the contents of my C++ Database Development (MIS Press, 1993).

End of self-serving plug--it's the Pournellian imperative rearing its ugly mug again. The point of all this is that as I was stepping through the overhead slides and holding forth on the code in that book, I kept tripping over how much easier everything I was describing would have been if the C++ compilers had included templates a year ago when I wrote it. I would explain some complex and contrived base class that lets programmers wedge their designs into the parts and pieces of the database manager, and I could see as I spoke that there is a far better way. Using inheritance to add management to objects of a class is cumbersome, but it used to be the only way to do it. Inheritance should be used to add and inherit behavior. Templates should be used to wrap a management function around objects. I'm going to have to do a second edition.

Multimedia Prez

Another bonus of that seminar was an entertaining demonstration of Windows multimedia by Charles Petzold, the Windows programming guru who wrote the definitive book on the subject.

With a few simple demonstrations, Charles unlocked the MIDI puzzle that I posed a few months back. I came home and downloaded some of Charles's C programs from ZiffNet, and the shrouds of mystery fell away.

Charles demonstrated some impressive video and sound on a lunch box portable. He showed how the "not" could be removed from Nixon's "I am not a crook" quote by a simple swipe (pun intended) of the mouse. Then he ran an animation of Bill Gates's head on Ronald Reagan's body dancing to Mozart. He didn't pick on any Democrats. Maybe Mozart was a Democrat.

Fired up by Charles, I came home and got into MIDI again. Don Menza, a fine bebop tenor saxophone player had given me a transcription of Bill Clinton's saxophone solo on "Your Mama Don't Dance and Your Daddy Don't Rock 'n' Roll." Clinton played that at an inaugural party, sitting in with the band. I encoded the transcribed solo into a MIDI file and added electric piano accompaniment so a listener could find the context of the improvised solo. You can hear it yourself if you have a sound board and MIDI sequencer software. It's in the MIDI forum on CompuServe under the name CLNTON.ZIP.

After listening to the clip a few times, I decided to send it to Philippe Kahn. There's an open chair in the Turbo Jazz sax section after a Symantec defection, and I figure Bill might be looking for a gig in about three years.



[LISTING ONE]

// --------- ted.h
#ifndef TED_H
#define TED_H

#include "dflatpp.h"
#include "fileopen.h"
#include "tedtools.h"

#define Df void (DFWindow::*)()
#define Ap void (Application::*)()

extern MenuBarItem TedMenu[];
extern MenuSelection InsertCmd, WordWrapCmd;

// ------- Ted application definition
class Ted : public Application {
    MenuBar menubar;
    TedTools toolbar;
    String  fname;
    EditBox editor;
protected:
    virtual void Size(int x, int y);
    void BuildTitle();
    void TestChanged();
public:
    Ted();
    virtual ~Ted() {}
    // ----- menu command functions
    void CmNew();
    void CmOpen();
    void CmSave();
    void CmSaveAs();
    void CmInsert();
    void CmCut() {}
    void CmCopy() {}
    void CmPaste() {}
    void CmExit()   { CloseWindow(); }
    virtual void CloseWindow();
};
#endif


[LISTING TWO]


// ------------- ted.cpp
#include <fstream.h>
#include "ted.h"

static char untitled[] = "(untitled)";
main()
{
    Ted ma;
    ma.Execute();
    return 0;
}

// ---- construct application
Ted::Ted() :  menubar(TedMenu, this),
              toolbar(this),
              editor(ClientLeft(),
                     ClientTop(),
                     ClientHeight(),
                     ClientWidth(),
                     this),
              fname(untitled)
{
    SetAttribute(SIZEABLE | MOVEABLE);
    SetClearChar(' ');
    BuildTitle();
    Show();
    editor.SetFocus();
}
// ---- builds the title with the current document name
void Ted::BuildTitle()
{
    SetTitle(String("TED: ") + fname);
    Title();
}
// ---- File/New Menu Command
void Ted::CmNew()
{
    TestChanged();
    editor.ClearText();
    editor.Paint();
}
// ---- File/Open Menu Command
void Ted::CmOpen()
{
    TestChanged();
    FileOpen fo;
    fo.Execute();
    if (fo.OKExit())    {
        editor.ClearText();
        fname = String(fo.FileName());
        BuildTitle();
        editor.ClearChanged();
        ifstream tfile(fname);
        String ip(200);
        while (!tfile.eof())    {
            tfile.getline((char *) ip, 200);
            editor.AddText(ip);
        }
        editor.Paint();
    }
}
// ---- File/Save Menu Command
void Ted::CmSave()
{
    if (fname == String(untitled))
        CmSaveAs();
    if (fname != String(untitled))    {
        editor.ClearChanged();
        ofstream tfile(fname);
        tfile.write((char *)*editor.Text(),editor.TextLength());
    }
}
// ---- File/Save As Menu Command
void Ted::CmSaveAs()
{
    SaveAs sa;
    sa.Execute();
    if (sa.OKExit())    {
        fname = String(sa.FileName());
        BuildTitle();
        CmSave();
    }
}
// ---- Options/Insert Menu Command
void Ted::CmInsert()
{
    if (InsertCmd.isToggled())
        desktop.keyboard().SetInsertMode();
    else
        desktop.keyboard().ClearInsertMode();
}
// ----- resize the editor when the application resizes
void Ted::Size(int x, int y)
{
    editor.Hide();
    editor.Size(editor.Right()+(x-Right()),
                editor.Bottom()+(y-Bottom()));
    Application::Size(x, y);
    editor.Show();
}
// ---- test for changes to the document before discarding
void Ted::TestChanged()
{
    if (editor.Changed())    {
        String msg(fname + "has changed. Save?");
        if (YesNo(msg))
            CmSave();
    }
}
// ---- test for changes before closing
void Ted::CloseWindow()
{
    TestChanged();
    Application::CloseWindow();
}



[LISTING THREE]


// ---------- tedmenu.cpp
#include "dflatpp.h"
#include "ted.h"

// --------- MenuSelection objects
MenuSelection
    NewCmd      ("~New",           (Ap) &Ted::CmNew ),
    OpenCmd     ("~Open...",       (Ap) &Ted::CmOpen ),
    SaveCmd     ("~Save",          (Ap) &Ted::CmSave ),
    SaveAsCmd   ("Save ~As...",    (Ap) &Ted::CmSaveAs ),
    ExitCmd     ("E~xit   Alt+F4", (Ap) &Ted::CmExit,  ALT_F4 ),
    CutCmd      ("~Cut    Ctrl+X", (Ap) &Ted::CmCut,   CTRL_X ),
    CopyCmd     ("C~opy   Ctrl+C", (Ap) &Ted::CmCopy,  CTRL_C ),
    PasteCmd    ("~Paste  Ctrl+V", (Ap) &Ted::CmPaste, CTRL_V ),
    InsertCmd   ("~Insert Ins",  (Ap) &Ted::CmInsert, On, INS ),
    WordWrapCmd ("~Word wrap",     On );
// --------- File menu definition
MenuSelection *File[] = {
    &NewCmd,
    &OpenCmd,
    &SaveCmd,
    &SaveAsCmd,
    &SelectionSeparator,
    &ExitCmd,
    0
};
MenuSelection *Edit[] = {
    &CutCmd,
    &CopyCmd,
    &PasteCmd,
    0
};
MenuSelection *Options[] = {
    &InsertCmd,
    &WordWrapCmd,
    0
};
// --------- menu bar definition
MenuBarItem TedMenu[] = {
    MenuBarItem( "~File",    File    ),
    MenuBarItem( "~Edit",    Edit    ),
    MenuBarItem( "~Options", Options ),
    MenuBarItem( 0 )
};


[LISTING FOUR]


// ---------- tedtools.h
#ifndef TEDTOOLS_H
#define TEDTOOLS_H

#include "toolbar.h"

// -------- the TED toolbar
class TedTools : public ToolBar    {
    ToolButton newtool;
    ToolButton opentool;
    ToolButton savetool;
public:
    TedTools(DFWindow *par);
};
#endif



[LISTING FIVE]


// ---------- tedtools.cpp
#include "ted.h"

// -------- the TED toolbar
TedTools::TedTools(DFWindow *par) : ToolBar(par),
            newtool("New",   (DFWindow *) this),
            opentool("Open", (DFWindow *) this),
            savetool("Save", (DFWindow *) this)
{
    newtool.SetButtonFunction(this->Parent(),
        (Df) (Ap) &Ted::CmNew);
    opentool.SetButtonFunction(this->Parent(),
        (Df) (Ap) &Ted::CmOpen);
    savetool.SetButtonFunction(this->Parent(),
        (Df) (Ap) &Ted::CmSave);
}




[LISTING SIX]


// -------- toolbar.h
#ifndef TOOLBAR_H
#define TOOLBAR_H

#include "pbutton.h"

const COLORS ToolBarBG = BLUE;

// ------ Toolbar class
class ToolBar : public DFWindow {
    void ParentSized(int xdif, int ydif);
public:
    ToolBar(DFWindow *par);
};
// ------- Toolbar button class
class ToolButton : public PushButton    {
    void SetColors();
    void InitWindow(char *lbl);
    DFWindow *oldFocus;
    virtual void ButtonCommand();
    virtual Bool SetFocus();
public:
    ToolButton(char *lbl, int lf, int tp, DFWindow *par=0);
    ToolButton(char *lbl, DFWindow *par=0);
    virtual void Paint();
    virtual void Border();
};
#endif


[LISTING SEVEN]


// ------------ toolbar.cpp
#include "toolbar.h"

// ----- construct a Toolbar
ToolBar::ToolBar(DFWindow *par) : DFWindow(par)
{
    windowtype = ToolbarWindow;
    if (par != 0)    {
        // --- put it into the application window
        Move(par->ClientLeft(), par->ClientTop());
        Size(par->ClientRight(), par->ClientTop()+2);
        par->SetAttribute(TOOLBAR);
    }
    colors.fg = colors.bg = ToolBarBG;
}
// ---- resize the menubar when the application window resizes
void ToolBar::ParentSized(int xdif, int)
{
    Size(Right()+xdif, Bottom());
}


[LISTING EIGHT]



// ---------- toolbutt.cpp
#include "toolbar.h"
#include "desktop.h"

// ------- various color patterns
static Color EnabledColor = {
    LIGHTGRAY,            // fg
    ToolBarBG,            // bg
    WHITE,                // selected fg
    ToolBarBG,            // selected bg
    CYAN,                 // frame fg
    ToolBarBG,            // frame bg
    WHITE,                // highlighted fg
    ToolBarBG             // highlighted bg
};
static Color PressedColor = {
    BLACK,                // fg
    ToolBarBG,            // bg
    ToolBarBG,            // selected fg
    ToolBarBG,            // selected bg
    CYAN,                 // frame fg
    ToolBarBG,            // frame bg
    ToolBarBG,            // highlighted fg
    ToolBarBG             // highlighted bg
};
static Color DisabledColor = {
    BLACK,                // fg
    ToolBarBG,            // bg
    BLACK,                // selected fg
    ToolBarBG,            // selected bg
    BLACK,                // frame fg
    ToolBarBG,            // frame bg
    BLACK,                // highlighted fg
    ToolBarBG             // highlighted bg
};
// ---- pressed and unpressed frame corner characters
const int PRESSED_NE   = 184;
const int PRESSED_SW   = 211;
const int UNPRESSED_NE = 183;
const int UNPRESSED_SW = 212;
// ----- button frame when pressed
static BoxLines PressedBorder = {
    FOCUS_NW,
    FOCUS_LINE,
    PRESSED_NE,
    SIDE,
    SE,
    LINE,
    PRESSED_SW,
    FOCUS_SIDE,
};
// ----- button frame when not pressed
static BoxLines UnPressedBorder = {
    NW,
    LINE,
    UNPRESSED_NE,
    FOCUS_SIDE,
    FOCUS_SE,
    FOCUS_LINE,
    UNPRESSED_SW,
    SIDE
};
// --- common constructor code
void ToolButton::InitWindow(char *lbl)
{
    oldFocus = 0;
    windowtype = ToolButtonWindow;
    Size(Left()+5, Top()+2);
    ClearAttribute(SHADOW);
    SetAttribute(BORDER);
    SetText(lbl);
    SetColors();
}
// --------- construct a Toolbar button specifying position
ToolButton::ToolButton(char *lbl, int lf, int tp, DFWindow *par)
                : PushButton(lbl, lf, tp, par)
{
    InitWindow(lbl);
}
// ------ construct a Toolbar button, self-positioning
ToolButton::ToolButton(char *lbl, DFWindow *par)
                : PushButton(lbl, 0, 0, par)
{
    InitWindow(lbl);
    if (par != 0 && par->WindowType() == ToolbarWindow)    {
        int btcount = 0;
        DFWindow *Wnd = par->First();
        while (Wnd != 0 && Wnd != this)    {
            if (Wnd->WindowType() == ToolButtonWindow)
                btcount++;
            Wnd = Wnd->Next();
        }
        Move(par->Left()+btcount*6, par->Top());
    }
}
// ----- draw the button's frame
void ToolButton::Border()
{
    if (visible)    {
        if (pressed)    {
            colors = PressedColor;
            DrawBorder(PressedBorder);
        }
        else    {
            SetColors();
            DrawBorder(UnPressedBorder);
        }
    }
}
// -------- set the button's colors
void ToolButton::SetColors()
{
    if (isEnabled())
        colors = EnabledColor;
    else
        colors = DisabledColor;
}
// ------- paint the button
void ToolButton::Paint()
{
    SetColors();
    TextBox::Paint();
}
// -------- the button was pressed
void ToolButton::ButtonCommand()
{
    PushButton::ButtonCommand();
    if (oldFocus != 0)
        oldFocus->SetFocus();
    oldFocus = 0;
}
// ---- remember who had the focus before the button got it
Bool ToolButton::SetFocus()
{
    if (oldFocus == 0)
        oldFocus = desktop.InFocus();
    return PushButton::SetFocus();
}


Copyright © 1993, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.