The Design and Implementation of VIOWIN - Part 3

From EDM2
Jump to: navigation, search
The Design and Implementation of VIOWIN
Part: 1 2 3 4 5 6 7 8

The Fullscreen PM Subset

Written by Larry Salomon Jr.

Introduction

For my job, I once had to write an application that ran only when OS/2 booted from the floppy diskettes. Because I had no access to the functionality PM provides, I resorted to a line-oriented interface, where messages were displayed on the screen and scrolled up when necessary. It was a good interface, I thought; it was fully NLS enabled and had intelligent defaults so the user basically only had to type in the name of the application. Unfortunately, the Quality Assurance team didn't concur with my opinion. "We want a nice interface!" one exclaimed. "Yeah, one with different windows and such!" another shouted.

I was backed into a corner that I could only get out of one way.

This series describes the design and implementation of VIOWIN, a library that implements a subset of the Win APIs provided by PM for fullscreen sessions. The reasoning behind writing this series is that it provided me and will hopefully provide you with some unique insights into how a windowing system is developed; and since it is based on PM, your familiarity with the already defined interface will increase your capability to fully understand what is being described.

Obviously, this series assumes you have PM application development experience, but it isn't required.

Last Month

Last month, we looked at the design of the keyboard monitor which provides the WM_CHAR messages to the system and how the design lent itself to a more difficult implementation. This month, we will continue by looking at the remaining functions that are called from within the main() function.

Errata

Before we do anything, however, a correction needs to be made. In postKeyMsg() there was the following code:

case 0x37:
   usFlags|=KC_VIRTUALKEY;
   usVkey=VK_PRINTSCRN;
   break;

This code was written with the intent of allowing me to press the PrintScrn key and get a screen dump in a held printer queue using the IBMNULL printer driver. After you finish laughing at the stupidity of my assumption that this will do the trick, you will realize that 0x37 is the scan code of the "7" key and that it will not work if this code is left in. Thus, it was removed.

Classes

Since the first thing a PM application typically does after initialization is register a private window class or two, let's look at this code first.

SHORT EXPENTRY _findClass(PVWCLASSINFO pciInfo,PCHAR pchClass)
//-------------------------------------------------------------------------
// This function compares the name of the specified class to see if it
// matches the specified name.  It is called by CmnLstSearchRecord().
//
// Input:  pciInfo - points to a VWCLASSINFO structure
//         pchClass - points to the class name to compare with
// Returns:  0 if match, non-0 otherwise
//-------------------------------------------------------------------------
{
   return (strcmp(pciInfo->achName,pchClass)!=0);
}

BOOL EXPENTRY vwRegisterClass(PCHAR pchClass,PFNVWWP pfnWndProc)
//-------------------------------------------------------------------------
// This function registers the class with the name specified.
//
// Input:  pchClass - points to the name of the class
//         pfnWndProc - points to the window procedure for the class
// Returns:  TRUE if successful, FALSE otherwise
//-------------------------------------------------------------------------
{
   VWCLASSINFO ciInfo;

   if (hmqQueue==NULL) {
      return FALSE;
   } /* endif */

   if (CmnLstSearchRecord(CmnLstQueryRecord(habAnchor->hclClasses,0),
                          pchClass,
                          (PFNRECCOMP)_findClass)!=NULL) {
      return FALSE;
   } /* endif */

   strcpy(ciInfo.achName,pchClass);
   ciInfo.pfnWndProc=pfnWndProc;

   return CmnLstAddRecord(habAnchor->hclClasses,&ciInfo,LAR_TAIL,NULL);
}

This function is fairly trivial - it checks to see if vwInitialize() has been called by checking the value of hmqQueue. It then searches the class list to see if the class name is already used. See the documentation for the Common/2 library for more information about the linked-list routines. If the class was not found, it is added to the class list via CmnLstAddRecord().

Note!
You should note the deviations from PM: there are no class styles and there is no way to get more than the 8 bytes of window words as provided by the VWWND structure (see volume 2, issue 8).
There are two other trivial functions dealing with classes - vwQueryClassName() and vwQueryClassInfo() - which we will not discuss here, but are included in the sample source provided with this article.

Windows

The next thing that is done is to create a window, so let's look at vwCreateWindow() and vwDestroyWindow().

HVWWND EXPENTRY vwCreateWindow(PCHAR pchClass,
                               USHORT usId,
                               ULONG ulStyle,
                               PCHAR pchText,
                               LONG lX,
                               LONG lY,
                               ULONG ulCx,
                               ULONG ulCy,
                               LONG lForeClr,
                               LONG lBackClr)
//-------------------------------------------------------------------------
// This function creates a window.
//
// Input:  pchClass - points to the name of the class of the window
//         usId - specifies the window id.  If -1 is specified, the check
//                for other windows with the same id is not performed.
//         ulStyle - specifies the style of the window
//         pchText - points to the text of the window
//         lX, lY - specifies the lower left corner of the window, relative
//                  to the desktop
//         ulCx, ulCy - specifies the width and height of the window
//         lForeClr - specifies the VWCLR_* constant for the foreground color
//         lBackClr - specifies the VWCLR_* constant for the background color
// Returns:  window handle if successful, NULLHANDLE otherwise
//-------------------------------------------------------------------------
{
   PVWCLASSINFO pciClass;
   VWWND vwWnd;
   HVWWND hwndNew;
   BOOL bDesktop;

   if (hmqQueue==NULL) {
      return NULL;
   } /* endif */

   //----------------------------------------------------------------------
   // Make sure the class was registered
   //----------------------------------------------------------------------
   pciClass=(PVWCLASSINFO)CmnLstQueryRecord(habAnchor->hclClasses,0);
   pciClass=(PVWCLASSINFO)CmnLstSearchRecord(pciClass,
                                             pchClass,
                                             (PFNRECCOMP)_findClass);
   if (pciClass==NULL) {
      return NULL;
   } /* endif */

   memset(&vwWnd,0,sizeof(VWWND));
   vwWnd.ulSzStruct=sizeof(VWWND);
   vwWnd.pciClass=pciClass;

   //----------------------------------------------------------------------
   // If the window id is not -1, check for another window with this id.
   //----------------------------------------------------------------------
   if ((usId!=-1) && (vwWindowFromID(usId)!=NULL)) {
      return NULL;
   } /* endif */

   //----------------------------------------------------------------------
   // If there are no other windows, then this window's id *must* be
   // VWWID_DESKTOP.
   //----------------------------------------------------------------------
   if ((CmnLstQueryRecordCount(habAnchor->hclWindows)==0) &&
       (usId!=VWWID_DESKTOP)) {
      return NULL;
   } /* endif */

   vwWnd.usId=usId;
   vwWnd.ulStyle=ulStyle;
   vwWnd.swpSwp.lX=lX;
   vwWnd.swpSwp.lY=lY;
   vwWnd.swpSwp.ulCx=ulCx;
   vwWnd.swpSwp.ulCy=ulCy;
   vwWnd.lForeClr=lForeClr;
   vwWnd.lBackClr=lBackClr;

   if (!CmnLstAddRecord(habAnchor->hclWindows,
                        &vwWnd,
                        LAR_TAIL,
                        (PPVOID)&hwndNew)) {
      return NULL;
   } /* endif */

   //----------------------------------------------------------------------
   // If this is the desktop, set a flag so that we post it a WM_PAINT
   // instead of sending it the message.
   //----------------------------------------------------------------------
   bDesktop=(usId==VWWID_DESKTOP);

   if (bDesktop) {
      habAnchor->ulStatus|=VW_HABST_CREATINGDESK;
   } /* endif */

   //----------------------------------------------------------------------
   // Check for valid initialization
   //----------------------------------------------------------------------
   if (LONGFROMMR(vwSendMsg(hwndNew,WM_CREATE,0,0))==TRUE) {
      CmnLstDeleteRecord(habAnchor->hclWindows,hwndNew);
      habAnchor->ulStatus&=~VW_HABST_CREATINGDESK;
      return NULL;
   } /* endif */

   if (bDesktop) {
      habAnchor->ulStatus&=~VW_HABST_CREATINGDESK;
   } /* endif */

   vwWnd.pchText=NULL;
   vwSetWindowText(hwndNew,pchText);

   //----------------------------------------------------------------------
   // Paint
   //----------------------------------------------------------------------
   if ((habAnchor->ulStatus & VW_HABST_CREATINGDESK)==0) {
      vwSendMsg(hwndNew,WM_PAINT,0,0);
   } else {
      vwPostMsg(hwndNew,WM_PAINT,0,0);
   } /* endif */

   return hwndNew;
}

BOOL EXPENTRY vwDestroyWindow(HVWWND hwndWnd)
//-------------------------------------------------------------------------
// This function destroys a window
//
// Input:  hwndWnd - handle to the window
// Returns:  TRUE if successful, FALSE otherwise
//-------------------------------------------------------------------------
{
   if (hwndWnd==VWHWND_DESKTOP) {
      hwndWnd=vwWindowFromID(VWWID_DESKTOP);
   } /* endif */

   if (!vwIsWindow(hwndWnd)) {
      return FALSE;
   } /* endif */

   //----------------------------------------------------------------------
   // If this is the desktop window, traverse the window list and destroy
   // all of the children first.
   //----------------------------------------------------------------------
   if (vwQueryWindowUShort(hwndWnd,QWS_ID)==VWWID_DESKTOP) {
      CmnLstTraverseList(habAnchor->hclWindows,(PFNRECFUNC)destroyWindow);
      destroyWindow(hwndWnd);
   } else {
      destroyWindow(hwndWnd);
   } /* endif */

   return TRUE;
}
Note!
Note the deviations again from PM. In PM, the desktop is created when the system initializes itself. Since VIOWIN doesn't support parent-child relationships unless the parent is the desktop, I decided to let the application create the desktop of a class it chooses. The desktop is identified by a special id - VWWID_DESKTOP.
Note!
Since the desktop will usually create a number of child windows, the WM_PAINT message is posted and not sent to any children it creates, but is sent to itself, through the use of the VW_HABST_CREATINGDESK flag in habAnchor->ulStatus. The intent is to prevent the desktop from painting over its children because, if the message were posted, the desktop would receive it last.

Messages

The final thing that is done, inside the main() function at least, is to enter the message loop.

BOOL EXPENTRY vwGetMsg(PVWQMSG pqmMsg)
//-------------------------------------------------------------------------
// This function gets the next message from the circular queue.
//
// Input:  pqmMsg - points to the VWQMSG structure
// Output:  pqmMsg - points to the initialized VWQMSG structure
// Returns:  TRUE if successful, FALSE otherwise
//-------------------------------------------------------------------------
{
   if (hmqQueue==NULL) {
      return FALSE;
   } /* endif */

   while (hmqQueue->ulTail==hmqQueue->ulHead) {
      DosSleep(0);
   } /* endwhile */

   hmqQueue->ulHead++;
   if (hmqQueue->ulHead==VW_SIZEQUEUE) {
      hmqQueue->ulHead=0;
   } /* endif */

   *pqmMsg=hmqQueue->aqmMsgs[hmqQueue->ulHead];
   return (pqmMsg->ulMsg!=WM_QUIT);
}

BOOL EXPENTRY vwDispatchMsg(PVWQMSG pqmMsg)
//-------------------------------------------------------------------------

// This function calls the appropriate window procedure with the
appropriate

// parameters.
//
// Input:  pqmMsg - points to the initialized VWQMSG structure
// Returns:  TRUE if successful, FALSE otherwise
//-------------------------------------------------------------------------
{
   if (hmqQueue==NULL) {
      return FALSE;
   } /* endif */

   (*pqmMsg->hwndWnd->pciClass->pfnWndProc)(pqmMsg->hwndWnd,
                                            pqmMsg->ulMsg,
                                            pqmMsg->mpParm1,
                                            pqmMsg->mpParm2);
   return TRUE;
}

This Is Too Easy

By now, you're probably thinking, "Wake me up when we get to something that requires more intelligence than that of a second grade child." You are right; everything we've seen is quite easily done. However, I must stress that the linked-list routines, which I said before were used heavily, are not shown here and they would take up a good sized portion of the code (but that's why there are DLL's, correct?). Also, these are the base routines, so they should be simplistic; the hard stuff comes later with the timers and such.

In other words, be patient but (by the same token) don't expect too much.

You should be aware that we have seen the code for all of the functions that are used in a typical main() function in a PM application. Next month, we will look at more of the window management functions as well as vwPostMsg() and vwSendMsg() and other stuff.

The Design and Implementation of VIOWIN
Part: 1 2 3 4 5 6 7 8