Channels ▼


C Programming

Source Code Accompanies This Article. Download It Now.


I'm writing this in August, having just returned from the Tools USA '92 conference, a small gathering at the University of California, Santa Barbara, that addresses the technology of object-oriented languages and systems. The conference is hosted by Bertrand Meyer, the architect and purveyor of the Eiffel object-oriented development environment and the author of several books on object-oriented design.

Mike Floyd of DDJ and I were on the program with a workshop titled, "User Interface Design." It was our intention to discuss the issues raised by my experiences with D-Flat++. Much of that experience should interest object-oriented programmers and designers. The project involves porting a medium-sized C library to C++. It implements the CUA interface in C++. It reveals things about the use of a C++ class library as a user-interface API. There were about 20 attendees in the workshop when we started. Some questions with shows of hands told us several things about the group. They were all interested in user-interface design, but most of them did not know what CUA is, and few had any knowledge of C++. About two minutes into the technical discussion, I began to feel like the proprietor of Phil's on Murphy Brown--I wanted to install speed bumps to keep the customers from racing out. Obviously, the goals of our workshop were not clearly advertised.

Not so obvious was that very few Tools USA '92 attendees cared a whit about C++. That became evident later when I cruised the show and talked to people. These were clearly Eiffel constituents, and there was little that they wanted to learn from me. Many of them were MIS management types, and most of them had never heard of Dr. Dobbs Journal. At least that part got corrected.

The keynote address followed a recent trend, in which speakers use the podium to peddle their own products. In this case, the product was an object-oriented database with shades of SQL. To begin with, the speaker used a practice common among those who pitch object-oriented database-management systems. In comparing the object-oriented data model to the traditional relational model, they mislead their listeners about the abilities of relational databases. They create artificial deficiencies to bolster arguments for their product. This is unfortunate. The object-oriented database model--once we agree on just what that is--has its place, and it solves some problems that other data models do not support well. But no good purpose is served by negative campaigns to promote it. Nonetheless, this particular speaker did that and then followed the technical presentation with a lengthy, unabashed commercial for his product. I expected to open the conference proceedings and find a reader service number and a bingo card.

The conference provided lodging in the campus dorms. I had forgotten the Spartan life. I haven't had a cold shower since my own long-ago school days--nor felt the need for one.

In the balance, Tools USA '92 was informative and professional. I took out more than I contributed and recommend it to anyone who wants to improve their knowledge and keep up with the latest techniques in object-oriented design. The next edition is in Versailles, France in March of '93. That would be a nice one to attend. I wonder if they need anyone to park cars.

My seat-mates on the return trip to Orlando were two sisters, aged 10 and 8, on their way to Disney World. Their parents were in the row behind us. The sisters were nice, mannerly young ladies, but at every move their father would yell at them. "Sit up straight!" "Eat your lunch!" "Don't do that!" The girls would flinch, and I could sense that they were used to this harangue. Sometime during the trip one of the girls asked where I'd been. When I said I'd been to Santa Barbara, she asked if I worked for the state government. Her father works for the state, she said, and goes to Santa Barbara sometimes. "What does your father do?" I asked. She answered, "If you don't pay your taxes, he comes and gets your car."

Probably repossesses pacemakers, too.

D-Flat++ Goals

The D-Flat++ class library is well underway. Its design is largely influenced by my experiences with D-Flat. DF++ will support my original requirements better than D-Flat does, because it will be less, not more, and it will be in C++. The goals that launched D-Flat were for a DOS-based, text-mode CUA library in C to support some applications that I was developing. The library grew with features as I learned more about CUA and as readers responded. But when I look at my D-Flat applications, I realize that I never used many of its features. The example Memopad application uses more D-Flat features than any of my real applications. I wondered why, and I concluded: A 25x80 text-mode screen does not support full-blown CUA all that well. For example, why have a multiple-document interface with minimized windows on a text-mode screen? What good are simulated icons on such paltry screen real estate?

CUA is a moving target. IBM has modified their standard reference document at least once. I get e-mail messages that complain that D-Flat doesn't work exactly like this or that "CUA-compliant" program. Every program has its own interpretation. IBM's standard reference document is unambiguous, but no one seems to be reading it. Microsoft is redefining the standard with minor changes--improvements, actually--to the Windows 3.1 interface. Their version will, no doubt, prevail.

What are our requirements? If we need a full-blown C++ CUA package, we have several avenues. Microsoft's and Borland's application-frameworks packages are C++ classes that hook into the Windows API. Borland's Turbo Vision is a full-featured C++ class library that implements CUA in DOS text-mode. What do we need from D-Flat++ that we can't get elsewhere? We need a simple class library that implements the minimum features necessary to launch a single-user, single-document application. The CUA part will support menus, dialog boxes, and the usual controls.

The Desktop

This month we will look at the design of the D-Flat++ desktop and its input/output devices and discuss some DF++ concepts in general terms.

DF++ is an object-oriented design. An application will operate from within a global object called the "desktop." The DeskTop class contains one application-window object and the device objects--the mouse, keyboard, screen, cursor, speaker, and clock objects. The application program does not declare most of these objects. There is already a DeskTop object named desktop when the program starts up, and it already has all the devices except for the mouse if no mouse driver is installed. The application program does declare the application window, which automatically associates itself with the global desktop object.

A DF++ program will define its application by defining menus and dialog boxes and deriving a custom application window class from the base Application class. DF++ adopts the event-driven, message-based model of D-Flat, but uses the features of C++ to manage the messages. The application program sets up the application window and lets the desktop object collect events and dispatch messages. The difference is that the messages are class member functions that belong to the various window classes. The oblique SendMessage processing of D-Flat is unnecessary in DF++.

Listing One, page 182, is desktop.h, which defines the DeskTop class. The class includes pointers to the application window, the window that has the focus, and the window, if any, that has captured the focus.

Having the focus is a characteristic shared by most window libraries that use the desktop metaphor. A user has a screen with a number of windows on it. Some of them display information that the user needs to read; others provide ways for the user to enter information. There are menus, list boxes, edit boxes, buttons, and so on, and the user's attention is focused on just one of them at a time. That control window is said to "have the focus." When the user presses a key or moves or clicks the mouse, that event should go to the window that has the focus. The key might scroll a window's text, add the typed character to the window's text, push a button, or perform any of a number of things that the user can do. It might even cause another window to get the focus.

Sometimes a window object will capture the focus, which means that until the window releases it, none of the user's actions will be sent to another window. The desktop maintains a simple listhead of windows that have captured the focus. When a window captures the focus, it joins the list. When the window releases the focus, the desktop surrenders the focus to the window that had it last.

The desktop declares all the device objects. Listings Two through Seven, beginning on page 182, are screen.h, mouse.h, cursor.h, keyboard.h, speaker.h, and clock.h, which define the classes for the device objects. The application program will interrogate or modify these devices by sending messages to them through the desktop.

Events and Messages

The application calls the desktop's DispatchEvents function to start event and message processing. That function then calls the DispatchEvents functions of the sysmouse, syskeyboard, and sysclock objects. Those functions poll their respective devices and call the appropriate member function of the window that should get the message. The device object determines which window that should be. Keyboard messages go to the window that has captured the focus, if any. Otherwise they go either to the window that has the focus or to the application window. In all cases, the messages are sent as calls to the window's member function through one of the pointers in the desktop object. Mouse messages go to the window where the mouse is pointing unless a window has captured the mouse, in which case, the capturing window gets the message, regardless of where the mouse cursor points.

When a window object gets a message, it can do one of four things. First, the window object can process the message and return. Second, it can decide that it has no interest in the message but that some base class somewhere up the class hierarchy to which the window class belongs should process the message. Third, the window object can do a combination of the first two. Finally, it can intercept and reject the message, deciding that neither it nor its base classes should process the message.

Suppose that you have an EditBox class derived from a TextBox class (which you will, eventually), and that an object of that class has the focus. The user presses a key, and the EditBox class gets a keyboard message. Example 1 shows how the class would exercise the four options just discussed for message processing.

Example 1: Message processing.

  Void EditBox::Keyboard(int key)

          switch (key)   {
            case F1:
                 // --- process the F1 key
                 // ...
            case F2:
                 // --- pass the F2 key up the hierarchy
            case F3:
                 // --- process the F3 key
                 // ...
                 // --- pass the F3 key up the hierarchy
            case F4:
                 // --- intercept and reject the F4 key

Window objects can send messages to other window objects or to themselves. Menus pop down because of messages between windows, and application's processes get called by menu-selection messages. Later columns will describe these processes in detail. The concept of sending messages is not new to C++ programmers nor to programmers in Windows-like, event-driven environments. This project uses C++ messages to implement event-driven messages. So far in my development, the DF++ programs are simpler and more expressive than the D-Flat programs, no doubt because of the natural fit of C++ messages and event-driven user-interface messages.

The Cigar-box User-interface Paradigm

Mike Hall is a gentleman bartender in my hometown, and we have spent a lot of time together. Mike's current station is in a small lounge in a beachside resort hotel owned by some folks in England. They installed a homegrown point-of-sale system with terminals throughout the hotel. Its user interface is remarkable, to say the least. Their own programmer contrived it and steadfastly insists that the interface is so closely and tightly intertwined with the rest of the program that it cannot be modified. It takes Mike about 12 keystrokes on a custom array of plastic-covered function keys to ring up the sale of a beer. Every transaction returns to the top-level menu, and he has to start from the top no matter what. Often the printer goes off into the weeds and forgets what font it should use. A receipt can take a minute to print because the THANK YOU is printed in dot-matrix graphics letters a foot high. The program crashes a lot and forgets the cumulated total on a guest check, so Mike has to post it all over again. They told Mike that the system is their check-and-balance to match inventory with sales and control what the industry calls "shrinkage"--another word for employee theft. Mike told them that if they'd turn off the computer, give him a cigar box to put the cash in, and let the employees steal $1000.00 a month, they'd be money ahead.

by Al Stevens

<a name="029b_000a">

// ---------- desktop.h
#ifndef DESKTOP_H
#define DESKTOP_H

#include "screen.h"
#include "cursor.h"
#include "keyboard.h"
#include "mouse.h"
#include "speaker.h"
#include "clock.h"

class DFWindow;

class DeskTop {
    DFWindow *apwnd;        // application window
    DFWindow *infocus;      // current window with the focus
    DFWindow *firstcapture; // first window to capture the focus
    DFWindow *focuscapture; // current window with captured focus
    // ------- the desktop devices
    Screen   sysscreen;     // the system screen
    Mouse    sysmouse;      // the system mouse
    Keyboard syskeyboard;   // the system keyboard
    Cursor   syscursor;     // the system cursor
    Clock    sysclock;      // the system clock
    Speaker  sysspeaker;    // the system speaker
    DFWindow *ApplWnd() { return apwnd; }
    void SetApplication(DFWindow *ApWnd) { apwnd = ApWnd; }
    Bool DispatchEvents();
    DFWindow *InFocus()      { return infocus; }
    DFWindow *FocusCapture() { return focuscapture; }
    DFWindow *FirstCapture() { return firstcapture; }
    void SetFocus(DFWindow *wnd) { infocus = wnd; }
    void SetFocusCapture(DFWindow *wnd) { focuscapture = wnd; }
    void SetFirstCapture(DFWindow *wnd) { firstcapture = wnd; }
    // ------- the desktop devices
    Mouse&    mouse()       { return sysmouse;    }
    Screen&   screen()      { return sysscreen;   }
    Keyboard& keyboard()    { return syskeyboard; }
    Cursor&   cursor()      { return syscursor;   }
    Clock&    clock()       { return sysclock;    }
    Speaker&  speaker()     { return sysspeaker;  }
extern DeskTop desktop;


<a name="029b_000b">
<a name="029b_000c">
<a name="029b_000c">

// ----------- screen.h
#ifndef SCREEN_H
#define SCREEN_H

#include <dos.h>
#include "dflatdef.h"
#include "rectangl.h"

class Screen    {
    unsigned address;
    unsigned mode;
    unsigned page;
    unsigned height;
    unsigned width;
    union REGS regs;
    // ---- compute video offset address
    unsigned vad(int x, int y) { return y * (width*2) + x*2; }
    unsigned Height() { return height; }
    unsigned Width()  { return width; }
    unsigned Page()   { return page; }
    Bool isEGA();
    Bool isVGA();
    Bool isMono() { return (Bool) (mode == 7); }
    Bool isText() { return (Bool) (mode < 4);  }
    void Scroll(Rect &rc, int d, int fg, int bg);
    unsigned int GetVideoChar(int x, int y);
    void PutVideoChar(int x, int y, unsigned int c);
    void WriteVideoString(char *s,int x,int y,int fg,int bg);
    void SwapBuffer(Rect &rc, char *bf,
        Bool Hiding, Bool HasShadow, Bool isFrame);
    void GetBuffer(Rect &rc, char *bf);
    void PutBuffer(Rect &rc, char *bf);
const int VIDEO = 0x10;
inline int clr(int fg, int bg)
    return fg | (bg << 4);

<a name="029b_000d">
<a name="029b_000e">
<a name="029b_000e">

// ---------- mouse.h
#ifndef MOUSE_H
#define MOUSE_H

#include <dos.h>
#include "dfwindow.h"
#include "timer.h"

class Mouse    {
    Bool installed;      // True = mouse is installed
    char *statebuffer;   // mouse state buffer
    Timer doubletimer;   // mouse double-click timer
    Timer delaytimer;    // mouse typematic click timer
    int prevx;           // previous mouse x coordinate
    int prevy;           // previous mouse y coordinate
    int clickx;          // click x position
    int clicky;          // click y position
    int releasex;        // release x position
    int releasey;        // release y position
    union REGS regs;
    DFWindow *MouseWindow(int mx, int my);
    void CallMouse(int m1,int m2=0,int m3=0,int m4=0,unsigned es=0);
    void DispatchRelease();
    void DispatchMove();
    void DispatchLeftButton();
    Bool Installed() { return installed; }
    void GetPosition(int &x, int &y); // get mouse position
    void SetPosition(int x, int y);   // set mouse position
    Bool Moved();           // True if mouse has moved
    void Show();            // show the mouse cursor
    void Hide();            // hide the mouse cursor
    Bool LeftButton();     // True if left button is pressed
    Bool ButtonReleased(); // True if button was released
    void SetTravel(int minx, int maxx, int miny, int maxy);
    void DispatchEvent();
const int MOUSE          = 0x33;  // mouse interrupt vector
// -------- mouse commands
const int RESETMOUSE     =  0;
const int SHOWMOUSE      =  1;
const int HIDEMOUSE      =  2;
const int READMOUSE      =  3;
const int SETPOSITION    =  4;
const int BUTTONRELEASED =  6;
const int XLIMIT         =  7;
const int YLIMIT         =  8;
const int BUFFSIZE       = 21;
const int SAVESTATE      = 22;
const int RESTORESTATE   = 23;
// -------- timer delays for mouse repeat, double clicks
const int DELAYTICKS     =  1;
const int FIRSTDELAY     =  7;
const int DOUBLETICKS    =  5;


<a name="029b_000f">
<a name="029b_0010">
<a name="029b_0010">

// ------------- cursor.h
#ifndef CURSOR_H
#define CURSOR_H

// ------- video BIOS (0x10) functions
const int SETCURSORTYPE = 1;
const int SETCURSOR     = 2;
const int READCURSOR    = 3;
const int HIDECURSOR    = 0x20;

const int MAXSAVES = 50;  // depth of cursor save/restore stack
class Cursor {
    // --- cursor save/restore stack
    int cursorpos[MAXSAVES];
    int cursorshape[MAXSAVES];
    int cs;             // count of cursor saves in effect
    union REGS regs;
    void Cursor::GetCursor();
    void SetPosition(int x, int y);
    void GetPosition(int &x, int &y);
    void SetType(unsigned t);
    void normalcursor() { SetType(0x0607); }
    void Hide();
    void Show();
    void Save();
    void Restore();
    void SwapStack();
inline void swap(int a, int b)
    int x = a;
    a = b;
    b = x;

<a name="029b_0011">
<a name="029b_0012">
<a name="029b_0012">

// ------------ keyboard.h
#ifndef KEYBOARD_H
#define KEYBOARD_H

#include <dos.h>
#include "dflatdef.h"

class Keyboard    {
    union REGS regs;
    int shift;
    Keyboard() { shift = GetShift(); }
    Bool ShiftChanged();
    int ShiftState() { return shift = GetShift(); }
    int AltConvert(int);
    int GetKey();
    int GetShift();
    Bool KeyHit();
    void clearBIOSbuffer();
    void DispatchEvent();
const int KEYBOARDVECT = 9;
const int KEYBOARDPORT = 0x60;

inline void Keyboard::clearBIOSbuffer()
    *(int far *)(MK_FP(0x40,0x1a)) =
    *(int far *)(MK_FP(0x40,0x1c));
// ----- keyboard BIOS (0x16) functions
const int KEYBRD = 0x16;
const int READKB = 0;
const int KBSTAT = 1;

const int ZEROFLAG = 0x40;
const int OFFSET = 0x1000;

const int RUBOUT = 8;
const int BELL   = 7;
const int ESC    = 27;
const unsigned F1          = (187+OFFSET);
const unsigned F8          = (194+OFFSET);
const unsigned SHIFT_F8    = (219+OFFSET);
const unsigned F10         = (196+OFFSET);
const unsigned HOME        = (199+OFFSET);
const unsigned UP          = (200+OFFSET);
const unsigned PGUP        = (201+OFFSET);
const unsigned BS          = (203+OFFSET);
const unsigned FWD         = (205+OFFSET);
const unsigned END         = (207+OFFSET);
const unsigned DN          = (208+OFFSET);
const unsigned PGDN        = (209+OFFSET);
const unsigned INS         = (210+OFFSET);
const unsigned DEL         = (211+OFFSET);
const unsigned CTRL_HOME   = (247+OFFSET);
const unsigned CTRL_PGUP   = (132+OFFSET);
const unsigned CTRL_BS     = (243+OFFSET);
const unsigned CTRL_FIVE   = (143+OFFSET);
const unsigned CTRL_FWD    = (244+OFFSET);
const unsigned CTRL_END    = (245+OFFSET);
const unsigned CTRL_PGDN   = (246+OFFSET);
const unsigned SHIFT_HT    = (143+OFFSET);
const unsigned ALT_A       = (158+OFFSET);
const unsigned ALT_B       = (176+OFFSET);
const unsigned ALT_C       = (174+OFFSET);
const unsigned ALT_D       = (160+OFFSET);
const unsigned ALT_E       = (146+OFFSET);
const unsigned ALT_F       = (161+OFFSET);
const unsigned ALT_G       = (162+OFFSET);
const unsigned ALT_H       = (163+OFFSET);
const unsigned ALT_I       = (151+OFFSET);
const unsigned ALT_J       = (164+OFFSET);
const unsigned ALT_K       = (165+OFFSET);
const unsigned ALT_L       = (166+OFFSET);
const unsigned ALT_M       = (178+OFFSET);
const unsigned ALT_N       = (177+OFFSET);
const unsigned ALT_O       = (152+OFFSET);
const unsigned ALT_P       = (153+OFFSET);
const unsigned ALT_Q       = (144+OFFSET);
const unsigned ALT_R       = (147+OFFSET);
const unsigned ALT_S       = (159+OFFSET);
const unsigned ALT_T       = (148+OFFSET);
const unsigned ALT_U       = (150+OFFSET);
const unsigned ALT_V       = (175+OFFSET);
const unsigned ALT_W       = (145+OFFSET);
const unsigned ALT_X       = (173+OFFSET);
const unsigned ALT_Y       = (149+OFFSET);
const unsigned ALT_Z       = (172+OFFSET);
const unsigned ALT_1      = (0xf8+OFFSET);
const unsigned ALT_2      = (0xf9+OFFSET);
const unsigned ALT_3      = (0xfa+OFFSET);
const unsigned ALT_4      = (0xfb+OFFSET);
const unsigned ALT_5      = (0xfc+OFFSET);
const unsigned ALT_6      = (0xfd+OFFSET);
const unsigned ALT_7      = (0xfe+OFFSET);
const unsigned ALT_8      = (0xff+OFFSET);
const unsigned ALT_9      = (0x80+OFFSET);
const unsigned ALT_0      = (0x81+OFFSET);
const unsigned ALT_HYPHEN = (130+OFFSET);
const unsigned CTRL_F4    = (225+OFFSET);
const unsigned ALT_F4     = (235+OFFSET);
const unsigned ALT_F6     = (237+OFFSET);

        CTRL_Y,CTRL_Z };
// ------- BIOS shift key mask values
const int RIGHTSHIFT = 0x01;
const int LEFTSHIFT  = 0x02;
const int CTRLKEY    = 0x04;
const int ALTKEY     = 0x08;
const int SCROLLLOCK = 0x10;
const int NUMLOCK    = 0x20;
const int CAPSLOCK   = 0x40;
const int INSERTKEY  = 0x80;


<a name="029b_0013">
<a name="029b_0014">
<a name="029b_0014">

// --------- speaker.h
#ifndef SPEAKER_H
#define SPEAKER_H

class Speaker    {
    void Wait(int n);
    void Beep();
const int FREQUENCY = 100;
const long COUNT = 1193280L / FREQUENCY;


<a name="029b_0015">
<a name="029b_0029"><a name="029b_0016">
<a name="029b_0016">

// --------- clock.h
#ifndef CLOCK_H
#define CLOCK_H

#include "timer.h"

class Clock    {
    Timer clocktimer;
    void DispatchEvent();

Copyright © 1992, Dr. Dobb's Journal

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags

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

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

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

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

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

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