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

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


Channels ▼
RSS

C/C++

C Programming


JUL91: C PROGRAMMING

C PROGRAMMING

D-Flat Message Processing

This article contains the following executables: DFLAT.791 DFLAT3.ARC

AL STEVENS

This is the third installment in the continuing saga of D-Flat. Many of you have responded to this project. Many have downloaded the preliminary source code library from CompuServe and TelePath, have compiled it, and are sending me bug reports already.

One of the problems with the preliminary publication of the code is that no documentation exists yet for the D-Flat API's messages, macros, and functions. I'll be producing and posting the API and user's documentation Real Soon Now. In the meantime, you must use your hacking talents to ferret out what you need to know from the code. If you get stumped, send me questions on CompuServe.

Events, Messages, and Windows

This month's chapter addresses the event and message mechanisms that D-Flat uses. I devoted a column to event-driven architectures in the March 1991 issue. Following is a recap of how it works.

A program that uses the event-driven architecture reacts to events from the outside -- keystrokes, mouse actions, the clock. The system's software converts these hardware and user events into messages that it sends to the applications software. The applications software lies dormant waiting for a message to come along. When one does, the software processes it, and can send other messages to other parts of itself and to the systems software.

A window-based event-driven architecture uses the video window as the applications object to and from which messages pass. Windows receive and process messages, and they send messages to other windows and to the system. If you are not accustomed to this paradigm, it will not seem to make sense at first.

In the traditional function-oriented program, data items do not do such things as send and receive messages. They simply exist, and functions do things to them. In an event-driven program, events happen, and the system sends messages to windows. A window is a data item, an object. It receives and sends messages. How can that be? It isn't simple, but it's not much different from what you are used to. Instead of writing a function that does things to a window, you write a function that represents the window itself and that receives, processes, and sends messages.

This behavior of passing messages among windows and between windows and the system is the foundation of object-oriented programming, a paradigm that embodies other unique characteristics besides message passing. It is no wonder that most of the program development environments for Windows and similar event-driven platforms are based in object-oriented programming.

Here's a simple scenario. The application software establishes a window on the screen and then waits for something to happen. The systems software is in a loop watching the hardware. The user presses a key. That keystroke is an event. The systems software translates the keystroke into a message and sends the message to the window.

The window has an event processing function that receives and processes all its messages. It receives the keystroke message and does whatever a window of its class does with a keystroke. Perhaps it sends the system a message that says, "Where is the cursor positioned?" The system answers such messages. Perhaps the window establishes another window and sends it a message that says, "Take this text and display it."

There are three basic message types -- event messages, messages from windows to the system, and messages to windows from other windows or the system. Event messages report mouse, keyboard, and clock events to the windows. Messages from windows to the system include such actions as positioning and requesting the position of the mouse and keyboard cursor. Messages to windows tell the window to do something, such as repaint its data space or change its size. Messages to windows can also ask the window to tell the message sender something about the message receiver.

D-Flat, like other window-based systems, supports a set of standard window classes. Last month's column published the class definition code. You can build an entire application by using the standard menus, dialog boxes, list boxes, buttons, and so on. You can also derive new window classes from the existing ones and write window processing functions that manage the unique qualities of your new window class. A derived window class can retain some or all of the behavior of its base class. A window's behavior is a function of its reaction to messages. A derived window class can introduce and process new messages, and it can intercept and process existing messages that its base class would have processed in other ways. This ability to derive new window classes that override the behavior of their base class is another area where this architecture resembles object-oriented programming.

The Message Software

Listing One, page 146, is message.h. It begins with some message-related global definitions. MAXMESSAGES is the maximum number of messages that can be queued. At 50, it should be enough. FIRSTDELAY and DELAYTICKS controls the typematic-like behavior of the mouse when you hold the button down for dragging or scrolling actions. The values in these globals are clock ticks of 1/18 second. The system waits FIRSTDELAY ticks after you first press the button before it begins to repeat the button event. Then it waits DELAYTICKS ticks between each repetition of the event. The DOUBLETICKS value is the maximum number of ticks that may elapse between two clicks at the same screen position before the system declares a double-click event. Some mouse-driven programs provide for the user to modify these values. D-Flat does not do that, but if you decide that you want it to, you should change these global symbols into variables that your user can change.

The Messages

Message.h is the header file that defines the messages in a D-Flat application. If you derive new window classes and need to add messages, you would add them to this source file. It is not apparent from the message names and their comments whether the messages are event-based, messages from the windows to the system, or messages between windows. This distinction should become apparent as you use the messages in the programs that follow.

The first message is the START message. I haven't found a use for it yet, but it's in there in case I do. If it disappears from the code someday, I've given up on it. The messages are divided into process communication messages; window management messages; clock, keyboard, and mouse messages; messages for the various standard window classes; and dialog box messages.

Windows can send messages to the system and send or post messages to other windows including themselves. Last month's column described the Create Window function, which creates a window and returns its handle, a variable of type WINDOW. That handle is how a window identifies itself and the way that a message sender addresses the message to a window. If the message is for the system, the WINDOW address is the NULLWND constant value.

Every message includes a message identifier taken from the list in message.h. Besides the message identifier, every message has two long integer parameters that pass arguments along with the message. The content and meaning of the parameters depends on the message itself. Sometimes they are empty. Some messages use only the first parameter, others use both. Sometimes the parameters are pointers; other times they are integer values; still other times they are simple on/off indicators.

Listing Two, page 146, is message.c, the source code for event and message processing. It maintains an event queue and a message queue. The event queue collects mouse, keyboard, and clock events. The message queue collects messages. The message.c file has two interrupt functions. The first one, new-timer, is for the timer interrupt. It counts down the three software timers that support the events. One timer, the double-timer counter counts the ticks between mouse clicks to see if a double-click event has occurred. The delaytimer counter controls the delays between repeats of the button event when the user does not release the button. The clocktimer counter is for the one-second clock event.

The second interrupt function is new-crit. It intercepts critical error interrupts, posts the error, and posts the disk drive letter of the critical error into an error message. The TestCriticalError function displays that error message in a dialog box and retrieves the user's ignore or retry option. This technique prevents DOS from splashing its rude "Abort, Retry, or Ignore" message onto your orderly D-Flat screen.

The init_messages function initializes message processing. An applications program must call this function before it begins to wait for messages. The function initializes all the variables, attaches the interrupt vectors for the timer and the critical error handler, and posts the START message to the system.

Waiting for a Message

After a program has created a window and called init_messages, it can enter a loop that waits for messages. The loop looks like this:

     
while (dispatch_message( ))         
	;

The dispatch_message function is at the bottom of messages.c. As long as nothing sends the STOP message to the system, the dispatch_message function will return a true value, and your program will stay in the loop. When the loop breaks, message processing is done. You would have to recall init_messages to do any further message processing with dispatch_message.

Collecting Events

The dispatch_message function calls the collect_events function to collect any pending events and queue them. The collect_events functions watches the hardware, interprets the events, and queues them. Queued events consist of a MESSAGE code that identifies the event and two integer parameters.

If the clocktimer counter has run down, the collect_events function reads the time-of-day clock and posts a CLOCKTICK event into the event queue. The two parameters are the segment and offset of a string variable that contains a displayable copy of the time. The time display alternates between putting a colon and a space between the hour and minute so that the clock will appear to be ticking when you display the string.

If the keyboard shift key status has changed, the collect_events function queues a SHIFT_CHANGED event with the new shift status value as the first parameter. If the user typed a key, the collect_events function translates the keystroke into an event and queues it.

Mouse events are tricky. The program remembers the most recent mouse location. If you move the mouse, the collect_events function queues a MOUSE_MOVED event with the new mouse coordinates in the parameters. If the right button is down, the function posts a RIGHT_BUTTON event which also contains the screen coordinates where the mouse was when you pressed the button. If the left button is down and the mouse has moved, this is a new button press as far as the software is concerned, and it queues a LEFT_BUTTON event with the screen coordinates. It also turns off the doubletimer and sets the delaytimer.

If you released the mouse button, the function kills the delaytimer and starts the doubletimer running. If you press the left button in the same place again before the timer runs down, that is a double-click.

If the left button is down and the mouse has not moved, one of two things might have happened. You might just be holding the button down. If you do and the delaytimer runs down, the function posts another LEFT_BUTTON event and restarts the delaytimer. If the doubletimer is running, however, there have been two clicks in the span of the timer's life, and the function disables the timer and queues the DOUBLE_CLICK event.

Turning Events into Messages

After calling the collect_events function, the dispatch_message function looks to see if there are any events in the event queue. As long as there are, it dequeues them, translates them into messages, and sends the messages to the windows that should get them. Mouse messages usually go to the window in which the mouse event occurred as determined by the screen coordinates of the event. The exception is when a different window has captured all mouse events regardless of where they hit.

Keyboard messages go to the window that presently has the "focus," which is the window into which the user is typing data. The in-focus window is the one that gets the user's attention. It is displayed on top of any other windows and, if it is a window that accepts typing, the keyboard cursor is in it. If no such window is in focus, the keyboard events go to whatever window does have the focus, and it is that window's responsibility to see that the keyboard gets processed or ignored, whichever is appropriate.

If the LEFT_BUTTON mouse event is going to a window that does not have the focus, the dispatch_message function sends the window a SETFOCUS message to tell it to take the focus. This procedure is what allows you to bring a window to the top and give it the input focus by clicking anywhere within its space.

The dispatch_message function sends CLOCKTICK messages to whichever window has captured the clock by sending itself the CAPTURE_CLOCK message.

Posting and Sending Messages

The message queue is where the system and windows post messages to go to windows. They do that by calling the PostMessage function, which accepts a WINDOW handle, the MESSAGE identifier, and the two message parameters. The dispatch_message function dequeues those messages and sends them through the SendMessage function, which takes the same parameters. If the message is a STOP or ENDDIALOG message, the dispatch_message returns a false value to tell the message processing loop of the application or the dialog box manager to stop.

The system and windows send messages to each other with the SendMessage function. It differs from the PostMessage function in that the messages get sent right away. If you call SendMessage the system will not return to the calling function until the message has been sent and processed. If you call PostMessage, the message will go into the message queue and will not be sent until the next time through the message-dispatching loop. If a message's function is to return a value, you must use SendMessage. A posted message returns nothing to the window that posts it.

The SendMessage function processes the messages that are for the system rather than other windows. These include the STOP, CAPTURE_CLOCK, RELEASE_CLOCK, KEYBOARD_CURSOR, CAPTURE_KEYBOARD, RELEASE_KEYBOARD, CURRENT_KEYBOARD_CURSOR, SAVE_CURSOR, RESTORE_CURSOR, HIDE_CURSOR, SHOW_CURSOR, MOUSE_INSTALLED, SHOW_MOUSE, HIDE_MOUSE, MOUSE_CURSOR, CURRENT_MOUSE_CURSOR, WAITMOUSE, TESTMOUSE, CAPTURE_MOUSE, and RELEASE_MOUSE messages. These messages either tell the system to change something in the hardware, ask for some hardware-related values, or capture and release input devices.

Changes to D-Flat Code

It is inevitable. A project of this size grows and changes, and sometimes you have to backtrack. I made one small addition to the window structure in dflat.h, published in the May, 1991 issue. Insert this variable immediately following the condition integer.

int restored_attrib; /* attributes when restored */

Add an assignment of zero to the variable in the CreateWindow function in window.c, too. This variable saves a window's attributes word when the window is minimized or maximized. The minimize and maximize procedures can then turn off some attributes that are inappropriate in a minimized or maximized window. The restore procedure restores the attribute word when it restores the window.

Several readers asked why the screen height was stuck at 25 lines when the PC supports several other configurations. Change this global definition in system.h from two months ago.

#define SCREENHEIGHT (peekb(0x40, 0x84)+1)

The former version of the global was set to 25. This version gets the current screen height from the BIOS RAM data area. One reader reports that this change delivers a screen height of 8 on his Hercules video system. If you have this problem, return the statement to its earlier value.

Of course, all the changes I mention in the column are in the D-Flat source code package that you can download.

What Will it Look Like?

Even though we are in the third month of code, you can't do anything with the programs that have been published so far. There are dependencies in the source modules for several months to come. You can download the full package, use the example memopad program, and perhaps branch out on your own. To help you decide to do that, Figure 1 shows a memopad screen with two open editor windows and a menu popped down.

How to Get D-Flat Now

The complete source code package for D-Flat is on CompuServe in Library 0 of the DDJ Forum and on TelePath. Its name is DFLATn.ARC, where n is an integer that represents a loosely assigned version number. The library is a preliminary version of the package but one that works. I will replace this file over the months as the code changes. At present, everything compiles and works with Turbo C 2.0 and Microsoft C 6.0. There is a makefile that the make utilities of both compilers accept, and there is one example program, the MEMO-PAD program. If you want to discuss D-Flat with me, my CompuServe ID is 71101,1262, and I monitor the DDJ Forum daily.

Them That Can, Does...

My pal Aubrey Sears works at NASA's Goddart Space Flight Center and has the run of their computer rooms. He gets into all kinds of high-tech stuff. One time he showed me a roomful of water-cooled mainframe. He said the service technician had to have a plumber's license as well as being certified on the behemoth. Aubrey's been learning C. He uses a mainframe C training manual, which is chock full of examples such as the one in Example 1. The manual says that this exercise demonstrates the left-to-right evaluation behavior of the comma-separated expression. This example does more than that.

Example 1: A coding example from a mainframe C training manual

  #include <stdio.h>
  main()
  {

      int x=1,y=1;
      x = (y++,y+3);
      printf("x=%d,y=%d\n",x,y);
      printf("++y=%d,x=%d\n",(--x,++y), x);

  }

Aubrey runs these programs on every computer he can get his hands on, which is an impressive suite of hardware. He wondered why C compilers for the Cray, the Sun, the IBM 3081, the VAX, and the PC disagree about the value printed for x in the second printf. Some display 4, the others display 5.

The C language has never mandated the order in which a function call's arguments are evaluated. The second argument to the printf statement decrements x. The third argument is x. If the compiler evaluates arguments right-to-left, printf will display 5 because that's what x was before the statement. If the compiler evaluates left-to-right, the value will be 4 because x decrements in the second argument before it gets passed in the third.

"Which value is right?" asks Aubrey. Both, of course. What's wrong? The example is wrong. Programmers should not write code like that example. It is a solid demonstration of nonportable code. Writers of so-called C training manuals should know better.

The Programmer's Soap Box: Drug Testing and Patent Medicine

At the tender age of 18, I took a polygraph test to get my first job. They wanted to make sure that I was in no way a threat to the national interest. In the 1950s, teenagers weren't as hip and experienced as they are today, and I didn't have enough sinning under my belt -- or over my belt, for that matter -- to jiggle a polygraph pen or titillate an examiner. I passed that test and became a computer programmer. But I left that examination room a changed person. I'm not sure that the machine really worked beyond its ability to intimidate the examinee, but I am convinced that those people violated my privacy by rummaging around in my brain.

Employers of yore insisted on good grooming, too. If your hair grew too long, your boss would suggest a trip to the barbershop. Seems odd today, but that's how things were, and nobody objected.

In the years since, the nation has perceived a growing drug problem, and the same kind of hysteria that gave employers unregulated access to your thoughts now lets them peek into other parts of your body as well. These invasions include tests of your bodily extracts--tests that they conduct for the purposes of granting or continuing your employment. Over the years, I missed out on several choice jobs simply because I would not submit to polygraphs and drug testing. But I never lost one because I wouldn't get a haircut.

Now those two conditions of employment have converged. You can wear your hair any way you like, but you must give a lock of it to your boss. No, not for your CEO to wear in a locket under his vest next to his cold, cold heart. Not for any reason like that. They want your hair for drug testing.

Kirchman Corporation of Altamonte Springs, Florida fired a programmer who refused to allow them to take a lock of her hair. Anita Nabors protested the hair drug test on two grounds. She objected to them messing with her looks by chopping off part of her coiffure, and she objected because the hair test is not universally endorsed as a reliable drug-screening technique. She was afraid that a faulty test result would jeopardize her job and reputation. She offered to submit to a blood or urine test, equally intrusive in my view, but the company said no and sent the six-year, "exemplary" employee packing.

Did they think she'd been hitting the drugs? No, it was just a new policy that everyone had to comply with or be canned. "Cower to" would be a better way to put it. Clip your bob or lose your job. Kirchman develops banking software, and one day they just up and decided that their programs had to be drugfree. All those banks feel better knowing that the programmers who write the software just say no, don't you see?

Anita Nabors sued Kirchman. I'm rooting for her. She tells me that since she left, they dropped the drug-screening program, even for new employees. That's how well it was working. But Anita hasn't found work yet, and they haven't offered to reinstate her. If you need a top Cobol programmer who is learning about PCs and whose courage and character are as intact as her hairdo, send an inquiry to DDJ and I'll forward it to her.

Before you follow Anita's brave example, however, be aware that the same mean-spirited mentality that claims rights to your hair, blood, urine, and spit will, if you object, brand you a probable drug addict or AIDS carrier who doesn't want to get caught. Why else would you mind if they poke around in your juices? You must have something to hide.

On to another plank. Last week I read that AT&T is trying to enforce a patent on a software algorithm that X-Window uses. AT&T claims rights to an algorithm that saves bitmapped images in memory so that a new window can pop up and pop down later. They call it "backing store." They are notifying developers of X-Window systems that there is a license fee for using this technology. I wonder if other programs -- Windows, GEM, and NewWave, for example -- that use similar algorithms will fall before the same sword? Could be. But beware, AT&T. Your blade is two-edged. This is going to cost you. I'm switching to US Sprint. Not only do I protest your use of software patents, but Murphy Brown is a sight better looking than Dennis Ritchie.


_C PROGRAMMING COLUMN_
by Al Stevens


[LISTING ONE]
<a name="019c_0012">

/* ----------- message.h ------------ */

#ifndef MESSAGES_H
#define MESSGAES_H

#define MAXMESSAGES 50
#define DELAYTICKS 1
#define FIRSTDELAY 7
#define DOUBLETICKS 5

typedef enum messages {
    /* ------------- process communication messages --------- */
    START,                  /* start message processing       */
    STOP,                   /* stop message processing        */
    COMMAND,                /* send a command to a window     */
    /* ------------- window management messages ------------- */
    CREATE_WINDOW,          /* create a window                */
    SHOW_WINDOW,            /* show a window                  */
    HIDE_WINDOW,            /* hide a window                  */
    CLOSE_WINDOW,           /* delete a window                */
    SETFOCUS,               /* set and clear the focus        */
    PAINT,                  /* paint the window's data space  */
    BORDER,                 /* paint the window's border      */
    TITLE,                  /* display the window's title     */
    MOVE,                   /* move the window                */
    SIZE,                   /* change the window's size       */
    MAXIMIZE,               /* maximize the window            */
    MINIMIZE,               /* minimize the window            */
    RESTORE,                /* restore the window             */
    INSIDE_WINDOW,          /* test x/y inside a window       */
    /* ------------- clock messages ------------------------- */
    CLOCKTICK,              /* the clock ticked               */
    CAPTURE_CLOCK,          /* capture clock into a window    */
    RELEASE_CLOCK,          /* release clock to the system    */
    /* ------------- keyboard and screen messages ----------- */
    KEYBOARD,               /* key was pressed                */
    CAPTURE_KEYBOARD,       /* capture keyboard into a window */
    RELEASE_KEYBOARD,       /* release keyboard to system     */
    KEYBOARD_CURSOR,        /* position the keyboard cursor   */
    CURRENT_KEYBOARD_CURSOR,/* read the cursor position       */
    HIDE_CURSOR,            /* hide the keyboard cursor       */
    SHOW_CURSOR,            /* display the keyboard cursor    */
    SAVE_CURSOR,            /* save the cursor's configuration*/
    RESTORE_CURSOR,         /* restore the saved cursor       */
    SHIFT_CHANGED,          /* the shift status changed       */
    /* ------------- mouse messages ------------------------- */
    MOUSE_INSTALLED,        /* test for mouse installed       */
    RIGHT_BUTTON,           /* right button pressed           */
    LEFT_BUTTON,            /* left button pressed            */
    DOUBLE_CLICK,           /* right button double-clicked    */
    MOUSE_MOVED,            /* mouse changed position         */
    BUTTON_RELEASED,        /* mouse button released          */
    CURRENT_MOUSE_CURSOR,   /* get mouse position             */
    MOUSE_CURSOR,           /* set mouse position             */
    SHOW_MOUSE,             /* make mouse cursor visible      */
    HIDE_MOUSE,             /* hide mouse cursor              */
    WAITMOUSE,              /* wait until button released     */
    TESTMOUSE,              /* test any mouse button pressed  */
    CAPTURE_MOUSE,          /* capture mouse into a window    */
    RELEASE_MOUSE,          /* release the mouse to system    */
    /* ------------- text box messages ---------------------- */
    ADDTEXT,                /* add text to the text box       */
    CLEARTEXT,              /* clear the edit box             */
    SETTEXT,                /* set address of text buffer     */
    SCROLL,                 /* vertical scroll of text box    */
    HORIZSCROLL,            /* horizontal scroll of text box  */
    /* ------------- edit box messages ---------------------- */
    EB_GETTEXT,             /* get text from an edit box      */
    EB_PUTTEXT,             /* put text into an edit box      */
    /* ------------- menubar messages ----------------------- */
    BUILDMENU,              /* build the menu display         */
    SELECTION,              /* menubar selection              */
    /* ------------- popdown messages ----------------------- */
    BUILD_SELECTIONS,       /* build the menu display         */
    CLOSE_POPDOWN,          /* tell parent popdown is closing */
    /* ------------- list box messages ---------------------- */
    LB_SELECTION,           /* sent to parent on selection    */
    LB_CHOOSE,              /* sent when user chooses         */
    LB_CURRENTSELECTION,    /* return the current selection   */
    LB_GETTEXT,             /* return the text of selection   */
    LB_SETSELECTION,        /* sets the listbox selection     */
    /* ------------- dialog box messages -------------------- */
    INITIATE_DIALOG,        /* begin a dialog                 */
    ENTERFOCUS,             /* tell DB control got focus      */
    LEAVEFOCUS,             /* tell DB control lost focus     */
    ENDDIALOG               /* end a dialog                   */
} MESSAGE;

/* --------- message prototypes ----------- */
void init_messages(void);
void PostMessage(WINDOW, MESSAGE, PARAM, PARAM);
int SendMessage(WINDOW, MESSAGE, PARAM, PARAM);
int dispatch_message(void);
int TestCriticalError(void);

#endif




<a name="019c_0013">
<a name="019c_0014">
[LISTING TWO]
<a name="019c_0014">

/* --------- message.c ---------- */

#include <stdio.h>
#include <dos.h>
#include <conio.h>
#include <string.h>
#include <time.h>
#include "dflat.h"

static int px = -1, py = -1;
static int pmx = -1, pmy = -1;
static int mx, my;

static int CriticalError;

/* ---------- event queue ---------- */
static struct events    {
    MESSAGE event;
    int mx;
    int my;
} EventQueue[MAXMESSAGES];

/* ---------- message queue --------- */
static struct msgs {
    WINDOW wnd;
    MESSAGE msg;
    PARAM p1;
    PARAM p2;
} MsgQueue[MAXMESSAGES];

static int EventQueueOnCtr;
static int EventQueueOffCtr;
static int EventQueueCtr;

static int MsgQueueOnCtr;
static int MsgQueueOffCtr;
static int MsgQueueCtr;

static int lagdelay = FIRSTDELAY;

static void (interrupt far *oldtimer)(void) = NULL;
WINDOW CaptureMouse = NULLWND;
WINDOW CaptureKeyboard = NULLWND;
static int NoChildCaptureMouse = FALSE;
static int NoChildCaptureKeyboard = FALSE;

static int doubletimer = -1;
static int delaytimer  = -1;
static int clocktimer  = -1;

WINDOW Cwnd = NULLWND;

/* ------- timer interrupt service routine ------- */
static void interrupt far newtimer(void)
{
    if (timer_running(doubletimer))
        countdown(doubletimer);
    if (timer_running(delaytimer))
        countdown(delaytimer);
    if (timer_running(clocktimer))
        countdown(clocktimer);
    oldtimer();
}

static char ermsg[] = "Error accessing drive x";

/* -------- test for critical errors --------- */
int TestCriticalError(void)
{
    int rtn = 0;
    if (CriticalError)    {
        rtn = 1;
        CriticalError = FALSE;
        if (TestErrorMessage(ermsg) == FALSE)
            rtn = 2;
    }
    return rtn;
}

/* ------ critical error interrupt service routine ------ */
static void interrupt far newcrit(IREGS ir)
{
    if (!(ir.ax & 0x8000))     {
        ermsg[sizeof(ermsg) - 2] = (ir.ax & 0xff) + 'A';
        CriticalError = TRUE;
    }
    ir.ax = 0;
}

/* ------------ initialize the message system --------- */
void init_messages(void)
{
    resetmouse();
    show_mousecursor();
    px = py = -1;
    pmx = pmy = -1;
    mx = my = 0;
    CaptureMouse = CaptureKeyboard = NULLWND;
    NoChildCaptureMouse = FALSE;
    NoChildCaptureKeyboard = FALSE;
    MsgQueueOnCtr = MsgQueueOffCtr = MsgQueueCtr = 0;
    EventQueueOnCtr = EventQueueOffCtr = EventQueueCtr = 0;
    if (oldtimer == NULL)    {
        oldtimer = getvect(TIMER);
        setvect(TIMER, newtimer);
    }
    setvect(CRIT, newcrit);
    PostMessage(NULLWND,START,0,0);
    lagdelay = FIRSTDELAY;
}

/* ----- post an event and parameters to event queue ---- */
static void PostEvent(MESSAGE event, int p1, int p2)
{
    if (EventQueueCtr != MAXMESSAGES)    {
        EventQueue[EventQueueOnCtr].event = event;
        EventQueue[EventQueueOnCtr].mx = p1;
        EventQueue[EventQueueOnCtr].my = p2;
        if (++EventQueueOnCtr == MAXMESSAGES)
            EventQueueOnCtr = 0;
        EventQueueCtr++;
    }
}

/* ------ collect mouse, clock, and keyboard events ----- */
static void near collect_events(void)
{
    struct tm *now;
    static int flipflop = FALSE;
    static char timestr[8];
    int hr, sk;
    static int ShiftKeys = 0;

    /* -------- test for a clock event (one/second) ------- */
    if (timed_out(clocktimer))    {
        /* ----- get the current time ----- */
        time_t t = time(NULL);
        now = localtime(&t);
        hr = now->tm_hour > 12 ?
             now->tm_hour - 12 :
             now->tm_hour;
        if (hr == 0)
            hr = 12;
        sprintf(timestr, "%2.2d:%02d", hr, now->tm_min);
        strcpy(timestr+5, now->tm_hour > 11 ? "pm" : "am");
        /* ------- blink the : at one-second intervals ----- */
        if (flipflop)
            *(timestr+2) = ' ';
        flipflop ^= TRUE;
        /* -------- reset the timer -------- */
        set_timer(clocktimer, 1);
        /* -------- post the clock event -------- */
        PostEvent(CLOCKTICK, FP_SEG(timestr), FP_OFF(timestr));
    }


    /* --------- keyboard events ---------- */
    if ((sk = getshift()) != ShiftKeys)    {
        ShiftKeys = sk;
        /* ---- the shift status changed ---- */
        PostEvent(SHIFT_CHANGED, sk, 0);
    }

    /* ---- build keyboard events for key combinations that
        BIOS doesn't report --------- */
    if (sk & ALTKEY)
        if (inp(0x60) == 14)    {
            while (!(inp(0x60) & 0x80))
                ;
            PostEvent(KEYBOARD, ALT_BS, sk);
        }
    if (sk & CTRLKEY)
        if (inp(0x60) == 82)    {
            while (!(inp(0x60) & 0x80))
                ;
            PostEvent(KEYBOARD, CTRL_INS, sk);
        }

    /* ----------- test for keystroke ------- */
    if (keyhit())    {
        static int cvt[] = {SHIFT_INS,END,DN,PGDN,BS,'5',
                        FWD,HOME,UP,PGUP};
        int c = getkey();

        /* -------- convert numeric pad keys ------- */
        if (sk & (LEFTSHIFT | RIGHTSHIFT))    {
            if (c >= '0' && c <= '9')
                c = cvt[c-'0'];
            else if (c == '.' || c == DEL)
                c = SHIFT_DEL;
            else if (c == INS)
                c = SHIFT_INS;
        }
        /* -------- clear the BIOS readahead buffer -------- */
        *(int far *)(MK_FP(0x40,0x1a)) =
            *(int far *)(MK_FP(0x40,0x1c));
        /* ---- if help key call generic help function ---- */
        if (c == F1)
            HelpFunction();
        else
            /* ------ post the keyboard event ------ */
            PostEvent(KEYBOARD, c, sk);
    }

    /* ------------ test for mouse events --------- */
    get_mouseposition(&mx, &my);
    if (mx != px || my != py)  {
        px = mx;
        py = my;
        PostEvent(MOUSE_MOVED, mx, my);
    }
    if (rightbutton())
        PostEvent(RIGHT_BUTTON, mx, my);
    if (leftbutton())    {
        if (mx == pmx && my == pmy)    {
            /* ---- same position as last left button ---- */
            if (timer_running(doubletimer))    {
                /* -- second click before double timeout -- */
                disable_timer(doubletimer);
                PostEvent(DOUBLE_CLICK, mx, my);
            }
            else if (!timer_running(delaytimer))    {
                /* ---- button held down a while ---- */
                delaytimer = lagdelay;
                lagdelay = DELAYTICKS;
                /* ---- post a typematic-like button ---- */
                PostEvent(LEFT_BUTTON, mx, my);
            }
        }
        else    {
            /* --------- new button press ------- */
            disable_timer(doubletimer);
            delaytimer = FIRSTDELAY;
            lagdelay = DELAYTICKS;
            PostEvent(LEFT_BUTTON, mx, my);
            pmx = mx;
            pmy = my;
        }
    }
    else
        lagdelay = FIRSTDELAY;
    if (button_releases())    {
        /* ------- the button was released -------- */
        doubletimer = DOUBLETICKS;
        PostEvent(BUTTON_RELEASED, mx, my);
        disable_timer(delaytimer);
    }
}

/* ----- post a message and parameters to msg queue ---- */
void PostMessage(WINDOW wnd, MESSAGE msg, PARAM p1, PARAM p2)
{
    if (MsgQueueCtr != MAXMESSAGES)    {
        MsgQueue[MsgQueueOnCtr].wnd = wnd;
        MsgQueue[MsgQueueOnCtr].msg = msg;
        MsgQueue[MsgQueueOnCtr].p1 = p1;
        MsgQueue[MsgQueueOnCtr].p2 = p2;
        if (++MsgQueueOnCtr == MAXMESSAGES)
            MsgQueueOnCtr = 0;
        MsgQueueCtr++;
    }
}

/* --------- send a message to a window ----------- */
int SendMessage(WINDOW wnd, MESSAGE msg, PARAM p1, PARAM p2)
{
    int rtn = TRUE, x, y;
    if (wnd != NULLWND)
        switch (msg)    {
            case PAINT:
            case BORDER:
            case RIGHT_BUTTON:
            case LEFT_BUTTON:
            case DOUBLE_CLICK:
            case BUTTON_RELEASED:
            case KEYBOARD:
            case SHIFT_CHANGED:
                /* ------- don't send these messages unless the
                    window is visible -------- */
                if (!isVisible(wnd))
                    break;
            default:
                rtn = wnd->wndproc(wnd, msg, p1, p2);
                break;
        }
    /* ----- window processor returned or the message was sent
        to no window at all (NULLWND) ----- */
    if (rtn != FALSE)    {
        /* --------- process messages that a window sends to the
            system itself ---------- */
        switch (msg)    {
            case STOP:
                hide_mousecursor();
                if (oldtimer != NULL)    {
                    setvect(TIMER, oldtimer);
                    oldtimer = NULL;
                }
                break;
            /* ------- clock messages --------- */
            case CAPTURE_CLOCK:
                Cwnd = wnd;
                set_timer(clocktimer, 0);
                break;
            case RELEASE_CLOCK:
                Cwnd = NULLWND;
                disable_timer(clocktimer);
                break;
            /* -------- keyboard messages ------- */
            case KEYBOARD_CURSOR:

                if (wnd == NULLWND)
                    cursor((int)p1, (int)p2);
                else
                 cursor(GetClientLeft(wnd)+(int)p1, GetClientTop(wnd)+(int)p2);
                break;
            case CAPTURE_KEYBOARD:
                if (p2)
                    ((WINDOW)p2)->PrevKeyboard=CaptureKeyboard;
                else
                    wnd->PrevKeyboard = CaptureKeyboard;
                CaptureKeyboard = wnd;
                NoChildCaptureKeyboard = (int)p1;
                break;
            case RELEASE_KEYBOARD:
                CaptureKeyboard = wnd->PrevKeyboard;
                NoChildCaptureKeyboard = FALSE;
                break;
            case CURRENT_KEYBOARD_CURSOR:
                curr_cursor(&x, &y);
                *(int*)p1 = x;
                *(int*)p2 = y;
                break;
            case SAVE_CURSOR:
                savecursor();
                break;
            case RESTORE_CURSOR:
                restorecursor();
                break;
            case HIDE_CURSOR:
                normalcursor();
                hidecursor();
                break;
            case SHOW_CURSOR:
                if (p1)
                    set_cursor_type(0x0106);
                else
                    set_cursor_type(0x0607);
                unhidecursor();
                break;
            /* -------- mouse messages -------- */
            case MOUSE_INSTALLED:
                rtn = mouse_installed();
                break;
            case SHOW_MOUSE:
                show_mousecursor();
                break;
            case HIDE_MOUSE:
                hide_mousecursor();
                break;
            case MOUSE_CURSOR:
                set_mouseposition((int)p1, (int)p2);
                break;
            case CURRENT_MOUSE_CURSOR:
                get_mouseposition((int*)p1,(int*)p2);
                break;
            case WAITMOUSE:
                waitformouse();
                break;
            case TESTMOUSE:
                rtn = mousebuttons();
                break;
            case CAPTURE_MOUSE:
                if (p2)
                    ((WINDOW)p2)->PrevMouse = CaptureMouse;
                else
                    wnd->PrevMouse = CaptureMouse;
                CaptureMouse = wnd;
                NoChildCaptureMouse = (int)p1;
                break;
            case RELEASE_MOUSE:
                CaptureMouse = wnd->PrevMouse;
                NoChildCaptureMouse = FALSE;
                break;
            default:
                break;
        }
    }
    return rtn;
}

/* ---- dispatch messages to the message proc function ---- */
int dispatch_message(void)
{
    WINDOW Mwnd, Kwnd;
    /* -------- collect mouse and keyboard events ------- */
    collect_events();
    /* --------- dequeue and process events -------- */
    while (EventQueueCtr > 0)  {
        struct events ev = EventQueue[EventQueueOffCtr];

        if (++EventQueueOffCtr == MAXMESSAGES)
            EventQueueOffCtr = 0;
        --EventQueueCtr;

        /* ------ get the window in which a
                        mouse event occurred ------ */
        Mwnd = inWindow(ev.mx, ev.my);

        /* ---- process mouse captures ----- */
        if (CaptureMouse != NULLWND)
            if (Mwnd == NULLWND ||
                    NoChildCaptureMouse ||
                        GetParent(Mwnd) != CaptureMouse)
                Mwnd = CaptureMouse;

        /* ------ get the window in which a
                        keyboard event occurred ------ */
        Kwnd = inFocus;

        /* ---- process keyboard captures ----- */
        if (CaptureKeyboard != NULLWND)
            if (Kwnd == NULLWND ||
                    NoChildCaptureKeyboard ||
                        GetParent(Kwnd) != CaptureKeyboard)
                Kwnd = CaptureKeyboard;

        /* -------- send mouse and keyboard messages to the
            window that should get them -------- */
        switch (ev.event)    {
            case SHIFT_CHANGED:
            case KEYBOARD:
                SendMessage(Kwnd, ev.event, ev.mx, ev.my);
                break;
            case LEFT_BUTTON:
                if (!CaptureMouse ||
                        (!NoChildCaptureMouse &&
                            GetParent(Mwnd) == CaptureMouse))
                    if (Mwnd != inFocus)
                        SendMessage(Mwnd, SETFOCUS, TRUE, 0);
            case BUTTON_RELEASED:
            case DOUBLE_CLICK:
            case RIGHT_BUTTON:
            case MOUSE_MOVED:
                SendMessage(Mwnd, ev.event, ev.mx, ev.my);
                break;
            case CLOCKTICK:
                SendMessage(Cwnd, ev.event,
                    (PARAM) MK_FP(ev.mx, ev.my), 0);
            default:
                break;
        }
    }
    /* ------ dequeue and process messages ----- */
    while (MsgQueueCtr > 0)  {
        struct msgs mq = MsgQueue[MsgQueueOffCtr];
        if (++MsgQueueOffCtr == MAXMESSAGES)
            MsgQueueOffCtr = 0;
        --MsgQueueCtr;
        SendMessage(mq.wnd, mq.msg, mq.p1, mq.p2);
        if (mq.msg == STOP || mq.msg == ENDDIALOG)
               return FALSE;
    }
    return TRUE;
}





<a name="019c_0015">
<a name="019c_0016">
[LISTING THREE]
<a name="019c_0016">

/* ------- display a window's border ----- */
void RepaintBorder(WINDOW wnd, RECT *rcc)
{
    int y;
    int lin, side, ne, nw, se, sw;
    RECT rc, clrc;

    if (!TestAttribute(wnd, HASBORDER))
        return;
    if (rcc == NULL)    {
        rc = SetRect(0, 0, WindowWidth(wnd)-1,
                WindowHeight(wnd)-1);
        if (TestAttribute(wnd, SHADOW))    {
            rc.rt++;
            rc.bt++;
        }
    }
    else
        rc = *rcc;
    clrc = rc;
    /* -------- adjust the client rectangle ------- */
    if (RectLeft(rc) == 0)
        --clrc.rt;
    else
        --clrc.lf;
    if (RectTop(rc) == 0)
        --clrc.bt;
    else
        --clrc.tp;
    RectRight(clrc) = min(RectRight(clrc), WindowWidth(wnd)-3);
    RectBottom(clrc) = min(RectBottom(clrc), WindowHeight(wnd)-3);
    if (wnd == inFocus)    {
        lin  = FOCUS_LINE;
        side = FOCUS_SIDE;
        ne   = FOCUS_NE;
        nw   = FOCUS_NW;
        se   = FOCUS_SE;
        sw   = FOCUS_SW;
    }
    else    {
        lin  = LINE;
        side = SIDE;
        ne   = NE;
        nw   = NW;
        se   = SE;
        sw   = SW;
    }
    line[WindowWidth(wnd)] = '\0';
    /* ---------- window title ------------ */
    if (RectTop(rc) == 0)
        if (RectLeft(rc) < WindowWidth(wnd))
            if (TestAttribute(wnd, TITLEBAR))
                DisplayTitle(wnd, clrc);
    foreground = FrameForeground(wnd);
    background = FrameBackground(wnd);
    /* -------- top frame corners --------- */
    if (RectTop(rc) == 0)    {
        if (RectLeft(rc) == 0)
            PutWindowChar(wnd, -1, -1, nw);
        if (RectLeft(rc) < RectRight(rc))    {
            if (RectRight(rc) >= WindowWidth(wnd)-1)
                PutWindowChar(wnd, WindowWidth(wnd)-2, -1, ne);

            if (TestAttribute(wnd, TITLEBAR) == 0)    {
                /* ----------- top line ------------- */
                memset(line,lin,WindowWidth(wnd)-1);
                line[RectRight(clrc)+1] = '\0';
                if (strlen(line+RectLeft(clrc)) > 1 ||
                                           TestAttribute(wnd, SHADOW) == 0)
                    writeline(wnd, line+RectLeft(clrc),
                            RectLeft(clrc), -1, FALSE);
            }
        }
    }
    /* ----------- window body ------------ */
    for (y = 0; y < ClientHeight(wnd); y++)    {
        int ch;
        if (y >= RectTop(clrc) && y <= RectBottom(clrc))    {
            if (RectLeft(rc) == 0)
                PutWindowChar(wnd, -1, y, side);
            if (RectLeft(rc) < RectRight(rc))    {
                if (RectRight(rc) >= ClientWidth(wnd))    {
                    if (TestAttribute(wnd, VSCROLLBAR))
                        ch = (    y == 0 ? UPSCROLLBOX      :
                                  y == WindowHeight(wnd)-3  ?
                                       DOWNSCROLLBOX        :
                                  y == wnd->VScrollBox      ?
                                       SCROLLBOXCHAR        :
                                       SCROLLBARCHAR );
                    else
                        ch = side;
                    PutWindowChar(wnd,WindowWidth(wnd)-2,y,ch);
                }
            }
            if (RectRight(rc) == WindowWidth(wnd))
                shadow_char(wnd, y);
        }
    }
    if (RectTop(rc) < RectBottom(rc) &&
            RectBottom(rc) >= WindowHeight(wnd)-1)    {
        /* -------- bottom frame corners ---------- */
        if (RectLeft(rc) == 0)
            PutWindowChar(wnd, -1, WindowHeight(wnd)-2, sw);
        if (RectRight(rc) >= WindowWidth(wnd)-1)
            PutWindowChar(wnd, WindowWidth(wnd)-2,
                WindowHeight(wnd)-2, se);
        /* ----------- bottom line ------------- */
        memset(line,lin,WindowWidth(wnd)-1);
        if (TestAttribute(wnd, HSCROLLBAR))    {
            line[0] = LEFTSCROLLBOX;
            line[WindowWidth(wnd)-3] = RIGHTSCROLLBOX;
            memset(line+1, SCROLLBARCHAR, WindowWidth(wnd)-4);
            line[wnd->HScrollBox] = SCROLLBOXCHAR;
        }
        line[RectRight(clrc)+1] = '\0';
        if (strlen(line+RectLeft(clrc)) > 1 || TestAttribute(wnd, SHADOW) == 0)
            writeline(wnd,
                line+RectLeft(clrc),
                RectLeft(clrc),
                WindowHeight(wnd)-2,
                FALSE);
        if (RectRight(rc) == WindowWidth(wnd))
            shadow_char(wnd, WindowHeight(wnd)-2);
    }
    if (RectBottom(rc) == WindowHeight(wnd))
        /* ---------- bottom shadow ------------- */
        shadowline(wnd, clrc);
}





Example 1:

#include <stdio.h>
main()
{
    int x=1,y=1;
    x = (y++,y+3);
    printf("x=%d,y=%d\n",x,y);
    printf("++y=%d,x=%d\n",(--x,++y),x);
}


Copyright © 1991, 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.